[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 889ef8af45 -s ours
am skip reason: contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/26076704
Change-Id: I44d69584ff4cebf422e7b3efa779cf01b131b798
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 501b438..7e57a3f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10,27 +10,18 @@
out: ["com/android/server/telecom/TelecomStatsLog.java"],
}
-filegroup {
- name: "Telecom-srcs",
+android_library {
+ name: "TelecomLib",
+ manifest: "AndroidManifestLib.xml",
srcs: [
"src/**/*.java",
":statslog-telecom-java-gen",
- ],
-}
-
-// Build the Telecom service.
-android_app {
- name: "Telecom",
- srcs: [
- ":Telecom-srcs",
"proto/**/*.proto",
],
static_libs: [
"androidx.annotation_annotation",
"androidx.core_core",
- ],
- libs: [
- "services",
+ "telecom_flags_core_java_lib",
],
resource_dirs: ["res"],
proto: {
@@ -39,6 +30,22 @@
output_params: ["optional_field_style=accessors"],
},
platform_apis: true,
+}
+
+
+// Build the Telecom service.
+android_app {
+ name: "Telecom",
+ srcs: [
+ ],
+ static_libs: [
+ "TelecomLib",
+ ],
+ libs: [
+ "services",
+ ],
+ resource_dirs: [],
+ platform_apis: true,
certificate: "platform",
privileged: true,
optimize: {
@@ -49,31 +56,24 @@
android_test {
name: "TelecomUnitTests",
static_libs: [
+ "TelecomLib",
"android-ex-camera2",
+ "flag-junit",
"guava",
"mockito-target-extended",
"androidx.test.rules",
"platform-test-annotations",
"androidx.legacy_legacy-support-core-ui",
"androidx.legacy_legacy-support-core-utils",
- "androidx.core_core",
"androidx.fragment_fragment",
"androidx.test.ext.junit",
"platform-compat-test-rules",
],
srcs: [
"tests/src/**/*.java",
- ":Telecom-srcs",
- "proto/**/*.proto",
],
- proto: {
- type: "nano",
- local_include_dirs: ["proto/"],
- output_params: ["optional_field_style=accessors"],
- },
resource_dirs: [
"tests/res",
- "res",
],
libs: [
"android.test.mock",
@@ -86,11 +86,6 @@
"libstaticjvmtiagent",
],
- aaptflags: [
- "--auto-add-overlay",
- "--extra-packages",
- "com.android.server.telecom",
- ],
manifest: "tests/AndroidManifest.xml",
optimize: {
enabled: false,
@@ -98,8 +93,8 @@
platform_apis: true,
certificate: "platform",
jacoco: {
- include_filter: ["com.android.server.telecom.*"],
- exclude_filter: ["com.android.server.telecom.tests.*"],
+ include_filter: ["com.android.server.telecom.**"],
+ exclude_filter: ["com.android.server.telecom.tests.**"],
},
test_suites: ["device-tests"],
defaults: ["SettingsLibDefaults"],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ab067d9..90e4bd9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,7 +28,6 @@
<!-- Prevents the activity manager from delaying any activity-start
requests by this package, including requests immediately after
the user presses "home". -->
- <uses-permission android:name="android.permission.BIND_CONNECTION_SERVICE"/>
<uses-permission android:name="android.permission.BIND_INCALL_SERVICE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
@@ -65,7 +64,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
- <uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
+ <uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -319,18 +318,6 @@
android:exported="false"
android:process=":ui"/>
- <service android:name=".components.BluetoothPhoneService"
- android:singleUser="true"
- android:process="system"
- android:exported="true">
- <intent-filter>
- <action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
- </intent-filter>
- <intent-filter>
- <action android:name="android.bluetooth.IBluetoothLeCallControlCallback" />
- </intent-filter>
- </service>
-
<service android:name=".components.TelecomService"
android:singleUser="true"
android:process="system"
diff --git a/AndroidManifestLib.xml b/AndroidManifestLib.xml
new file mode 100644
index 0000000..9b40f6b
--- /dev/null
+++ b/AndroidManifestLib.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest package="com.android.server.telecom">
+</manifest>
diff --git a/OWNERS b/OWNERS
index 97cc81f..7e68aea 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,6 @@
breadley@google.com
tgunn@google.com
xiaotonj@google.com
-chinmayd@google.com
tjstuart@google.com
rgreenwalt@google.com
pmadapurmath@google.com
diff --git a/flags/Android.bp b/flags/Android.bp
new file mode 100644
index 0000000..386831c
--- /dev/null
+++ b/flags/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+ name: "telecom_flags",
+ package: "com.android.server.telecom.flags",
+ srcs: [
+ "telecom_broadcast_flags.aconfig",
+ "telecom_ringer_flag_declarations.aconfig",
+ "telecom_api_flags.aconfig",
+ "telecom_call_filtering_flags.aconfig",
+ "telecom_incallservice_flags.aconfig",
+ "telecom_default_phone_account_flags.aconfig",
+ "telecom_callaudioroutestatemachine_flags.aconfig",
+ "telecom_call_flags.aconfig",
+ "telecom_calls_manager_flags.aconfig",
+ "telecom_anomaly_report_flags.aconfig",
+ "telecom_callaudiomodestatemachine_flags.aconfig",
+ "telecom_calllog_flags.aconfig",
+ "telecom_resolve_hidden_dependencies.aconfig",
+ "telecom_bluetoothroutemanager_flags.aconfig",
+ "telecom_work_profile_flags.aconfig",
+ "telecom_connection_service_wrapper_flags.aconfig",
+ ],
+}
+
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
new file mode 100644
index 0000000..dbacc08
--- /dev/null
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "gen_anom_report_on_focus_timeout"
+ namespace: "telecom"
+ description: "When getCurrentFocusCall times out, generate an anom. report"
+ bug: "309541253"
+}
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
new file mode 100644
index 0000000..21b83b2
--- /dev/null
+++ b/flags/telecom_api_flags.aconfig
@@ -0,0 +1,30 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "voip_app_actions_support"
+ namespace: "telecom"
+ description: "When set, Telecom support for additional VOIP application actions is active."
+ bug: "296934278"
+}
+
+flag {
+ name: "call_details_id_changes"
+ namespace: "telecom"
+ description: "When set, call details/extras id updates to Telecom APIs for Android V are active."
+ bug: "301713560"
+}
+
+flag{
+ name: "add_call_uri_for_missed_calls"
+ namespace: "telecom"
+ description: "The key is used for dialer apps to mark missed calls as read when it gets the notification on reboot."
+ bug: "292597423"
+}
+
+
+flag{
+ name: "set_mute_state"
+ namespace: "telecom"
+ description: "transactional calls need the ability to mute the call audio input"
+ bug: "310669304"
+}
diff --git a/flags/telecom_bluetoothroutemanager_flags.aconfig b/flags/telecom_bluetoothroutemanager_flags.aconfig
new file mode 100644
index 0000000..ddd8571
--- /dev/null
+++ b/flags/telecom_bluetoothroutemanager_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "use_actual_address_to_enter_connecting_state"
+ namespace: "telecom"
+ description: "Fix bugs that may add bluetooth device with null address."
+ bug: "306113816"
+}
+
diff --git a/flags/telecom_broadcast_flags.aconfig b/flags/telecom_broadcast_flags.aconfig
new file mode 100644
index 0000000..348d574
--- /dev/null
+++ b/flags/telecom_broadcast_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "is_new_outgoing_call_broadcast_unblocking"
+ namespace: "telecom"
+ description: "When set, the ACTION_NEW_OUTGOING_CALL broadcast is unblocking."
+ bug: "224550864"
+}
\ No newline at end of file
diff --git a/flags/telecom_call_filtering_flags.aconfig b/flags/telecom_call_filtering_flags.aconfig
new file mode 100644
index 0000000..95e74ce
--- /dev/null
+++ b/flags/telecom_call_filtering_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "skip_filter_phone_account_perform_dnd_filter"
+ namespace: "telecom"
+ description: "Gates whether to still perform Dnd filter when phone account has skip_filter call extra."
+ bug: "222333869"
+}
\ No newline at end of file
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
new file mode 100644
index 0000000..b5ea6a2
--- /dev/null
+++ b/flags/telecom_call_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "transactional_cs_verifier"
+ namespace: "telecom"
+ description: "verify connection service callbacks via a transaction"
+ bug: "309541257"
+}
\ No newline at end of file
diff --git a/flags/telecom_callaudiomodestatemachine_flags.aconfig b/flags/telecom_callaudiomodestatemachine_flags.aconfig
new file mode 100644
index 0000000..b263113
--- /dev/null
+++ b/flags/telecom_callaudiomodestatemachine_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "set_audio_mode_before_abandon_focus"
+ namespace: "telecom"
+ description: "Set audio mode to MODE_NORMAL before abandon the audio focus."
+ bug: "281841785"
+}
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
new file mode 100644
index 0000000..fe21c92
--- /dev/null
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -0,0 +1,71 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "available_routes_never_updated_after_set_system_audio_state"
+ namespace: "telecom"
+ description: "Fix supported routes wrongly include bluetooth issue."
+ bug: "292599751"
+}
+
+flag {
+ name: "use_refactored_audio_route_switching"
+ namespace: "telecom"
+ description: "Refactored audio routing"
+ bug: "306395598"
+}
+
+flag {
+ name: "ensure_audio_mode_updates_on_foreground_call_change"
+ namespace: "telecom"
+ description: "Ensure that the audio mode is updated anytime the foreground call changes."
+ bug: "289861657"
+}
+
+flag {
+ name: "ignore_auto_route_to_watch_device"
+ namespace: "telecom"
+ description: "Ignore auto routing to wearable devices."
+ bug: "294378768"
+}
+
+flag {
+ name: "transit_route_before_audio_disconnect_bt"
+ namespace: "telecom"
+ description: "Fix audio route transition issue on call disconnection when bt audio connected."
+ bug: "306113816"
+}
+
+flag {
+ name: "call_audio_communication_device_refactor"
+ namespace: "telecom"
+ description: "Refactor call audio set/clear communication device and include unsupported routes."
+ bug: "308968392"
+}
+
+flag {
+ name: "communication_device_protected_by_lock"
+ namespace: "telecom"
+ description: "Protect set/clear communication device operation with lock to avoid race condition."
+ bug: "303001133"
+}
+
+flag {
+ name: "reset_mute_when_entering_quiescent_bt_route"
+ namespace: "telecom"
+ description: "Reset mute state when entering quiescent bluetooth route."
+ bug: "311313250"
+}
+
+flag {
+ name: "update_route_mask_when_bt_connected"
+ namespace: "telecom"
+ description: "Update supported route mask when Bluetooth devices audio connected."
+ bug: "301695370"
+}
+
+flag {
+ name: "clear_communication_device_after_audio_ops_complete"
+ namespace: "telecom"
+ description: "Clear the requested communication device after the audio operations are completed."
+ bug: "315865533"
+}
diff --git a/flags/telecom_calllog_flags.aconfig b/flags/telecom_calllog_flags.aconfig
new file mode 100644
index 0000000..3ce7b63
--- /dev/null
+++ b/flags/telecom_calllog_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "telecom_log_external_wearable_calls"
+ namespace: "telecom"
+ description: "log external call if current device is a wearable one"
+ bug: "292600751"
+}
+
+flag {
+ name: "telecom_skip_log_based_on_extra"
+ namespace: "telecom"
+ description: "skipping logging a call based on passed extra"
+ bug: "295530944"
+}
diff --git a/flags/telecom_calls_manager_flags.aconfig b/flags/telecom_calls_manager_flags.aconfig
new file mode 100644
index 0000000..1a19480
--- /dev/null
+++ b/flags/telecom_calls_manager_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "use_improved_listener_order"
+ namespace: "telecom"
+ description: "Make InCallController the first listener to trigger"
+ bug: "24244713"
+}
+
+flag {
+ name: "fix_audio_flicker_for_outgoing_calls"
+ namespace: "telecom"
+ description: "This fix ensures the MO calls won't switch from Active to Quite b/c setDialing was not called"
+ bug: "309540769"
+}
diff --git a/flags/telecom_connection_service_wrapper_flags.aconfig b/flags/telecom_connection_service_wrapper_flags.aconfig
new file mode 100644
index 0000000..5f46c27
--- /dev/null
+++ b/flags/telecom_connection_service_wrapper_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "updated_rcs_call_count_tracking"
+ namespace: "telecom"
+ description: "Ensure that the associatedCallCount of CS and RCS is accurately being tracked."
+ bug: "286154316"
+}
\ No newline at end of file
diff --git a/flags/telecom_default_phone_account_flags.aconfig b/flags/telecom_default_phone_account_flags.aconfig
new file mode 100644
index 0000000..03f324c
--- /dev/null
+++ b/flags/telecom_default_phone_account_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "only_update_telephony_on_valid_sub_ids"
+ namespace: "telecom"
+ description: "For testing purposes, only update Telephony when the default calling subId is non-zero"
+ bug: "234846282"
+}
+
+flag {
+ name: "telephony_has_default_but_telecom_does_not"
+ namespace: "telecom"
+ description: "Telecom is requesting the user to select a sim account to place the outgoing call on but the user has a default account in the settings"
+ bug: "302397094"
+}
\ No newline at end of file
diff --git a/flags/telecom_incallservice_flags.aconfig b/flags/telecom_incallservice_flags.aconfig
new file mode 100644
index 0000000..e1a652b
--- /dev/null
+++ b/flags/telecom_incallservice_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "early_binding_to_incall_service"
+ namespace: "telecom"
+ description: "Binds to InCallServices when call requires no call filtering on watch"
+ bug: "282113261"
+}
+
+flag {
+ name: "ecc_keyguard"
+ namespace: "telecom"
+ description: "Ensure that users are able to return to call from keyguard UI for ECC"
+ bug: "306582821"
+}
\ No newline at end of file
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
new file mode 100644
index 0000000..ecc0123
--- /dev/null
+++ b/flags/telecom_resolve_hidden_dependencies.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "telecom_resolve_hidden_dependencies"
+ namespace: "android_platform_telecom"
+ description: "Mainland cleanup for hidden dependencies"
+ bug: "b/303440370"
+}
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
new file mode 100644
index 0000000..54748d0
--- /dev/null
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "use_device_provided_serialized_ringer_vibration"
+ namespace: "telecom"
+ description: "Gates whether to use a serialized, device-specific ring vibration."
+ bug: "282113261"
+}
\ No newline at end of file
diff --git a/flags/telecom_work_profile_flags.aconfig b/flags/telecom_work_profile_flags.aconfig
new file mode 100644
index 0000000..180af59
--- /dev/null
+++ b/flags/telecom_work_profile_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "associated_user_refactor_for_work_profile"
+ namespace: "telecom"
+ description: "Redefines the associated user for calls in the context of work profile support (U+)"
+ bug: "315035693"
+}
\ No newline at end of file
diff --git a/proguard.flags b/proguard.flags
index 635eba6..7c71a15 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -9,17 +9,3 @@
-keep class android.telecom.Log {
*;
}
-
-# Keep classes, annotations and members used by Lifecycle. Remove this once aapt2 is enabled
--keepattributes *Annotation*
-
--keep class * implements android.arch.lifecycle.LifecycleObserver {
-}
-
--keep class * implements android.arch.lifecycle.GeneratedAdapter {
- <init>(...);
-}
-
--keepclassmembers class ** {
- @android.arch.lifecycle.OnLifecycleEvent *;
-}
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index c9e5593..9ead5f4 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -19,7 +19,7 @@
<string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ဖုန်းခေါ်ဆိုမှုများ"</string>
<string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ဖုန်း"</string>
<string name="unknown" msgid="6993977514360123431">"မသိပါ"</string>
- <string name="notification_missedCallTitle" msgid="5060387047205532974">"လွတ်သွားသော ခေါ်ဆိုမှု"</string>
+ <string name="notification_missedCallTitle" msgid="5060387047205532974">"လွဲသွားသော ဖုန်းခေါ်မှု"</string>
<string name="notification_missedWorkCallTitle" msgid="6965463282259034953">"လွတ်သွားသည့် အလုပ်ဆိုင်ရာ ခေါ်ဆိုမှု"</string>
<string name="notification_missedCallsTitle" msgid="3910479625507893809">"လွဲသွားသော ဖုန်းခေါ်မှုများ"</string>
<string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> လွဲသွားသော ဖုန်းခေါ်မှုများ"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 6f3ebe3..787711b 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -24,7 +24,7 @@
<string name="notification_missedCallsTitle" msgid="3910479625507893809">"ମିସ୍ଡ କଲ୍"</string>
<string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>ଟି ମିସ୍ଡ କଲ୍"</string>
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ଙ୍କ ଠାରୁ ମିସ୍-କଲ୍ ମିଳିଛି"</string>
- <string name="notification_missedCall_call_back" msgid="7900333283939789732">"କଲବେକ କରନ୍ତୁ"</string>
+ <string name="notification_missedCall_call_back" msgid="7900333283939789732">"କଲବ୍ୟାକ୍ କରନ୍ତୁ"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"ମେସେଜ୍ ଦିଅନ୍ତୁ"</string>
<string name="notification_disconnectedCall_title" msgid="1790131923692416928">"କଲ୍ ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି"</string>
<string name="notification_disconnectedCall_body" msgid="600491714584417536">"ଏକ ଜରୁରୀକାଳୀନ କଲ୍ କରାଯାଇଥିବାରୁ <xliff:g id="CALLER">%s</xliff:g>ଙ୍କୁ କରାଯାଇଥିବା କଲ୍ ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି।"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 15f765b..c38a6ec 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -49,8 +49,10 @@
<bool name="grant_location_permission_enabled">false</bool>
<!-- When true, a simple full intensity on/off vibration pattern will be used when calls ring.
- When false, a fancy vibration pattern which ramps up and down will be used.
- Devices should overlay this value based on the type of vibration hardware they employ. -->
+
+ When false, the vibration effect serialized in the raw `default_ringtone_vibration_effect`
+ resource (under `frameworks/base/core/res/res/raw/`) is used. Devices should overlay this
+ value based on the type of vibration hardware they employ. -->
<bool name="use_simple_vibration_pattern">false</bool>
<!-- Threshold for the X+Y component of gravity needed for the device orientation to be
diff --git a/res/values/styles.xml b/res/values/styles.xml
index c8b24d3..cd608f5 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -61,6 +61,7 @@
</style>
<style name="BlockedNumbersButton" parent="BlockedNumbersTextPrimary2">
+ <item name="android:textColor">#202124</item>
</style>
<style name="BlockedNumbersTextHead1"
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
index e77184d..b6298e9 100644
--- a/res/xml/activity_blocked_numbers.xml
+++ b/res/xml/activity_blocked_numbers.xml
@@ -41,8 +41,8 @@
android:layout_height="wrap_content"
android:text="@string/non_primary_user"
android:paddingTop="@dimen/blocked_numbers_large_padding"
- android:paddingLeft="@dimen/blocked_numbers_large_padding"
- android:paddingRight="@dimen/blocked_numbers_large_padding"
+ android:paddingStart="@dimen/blocked_numbers_large_padding"
+ android:paddingEnd="@dimen/blocked_numbers_large_padding"
style="@style/BlockedNumbersTextPrimary2"
android:visibility="gone" />
@@ -62,8 +62,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/blocked_numbers_large_padding"
- android:paddingLeft="@dimen/blocked_numbers_large_padding"
- android:paddingRight="@dimen/blocked_numbers_large_padding">
+ android:paddingStart="@dimen/blocked_numbers_large_padding"
+ android:paddingEnd="@dimen/blocked_numbers_large_padding">
<TextView
android:layout_width="wrap_content"
diff --git a/res/xml/blocking_suppressed_butterbar.xml b/res/xml/blocking_suppressed_butterbar.xml
index 8b941b9..2947340 100644
--- a/res/xml/blocking_suppressed_butterbar.xml
+++ b/res/xml/blocking_suppressed_butterbar.xml
@@ -25,19 +25,19 @@
android:id="@+id/icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
android:paddingTop="@dimen/blocked_numbers_large_padding"
- android:paddingRight="@dimen/blocked_numbers_large_padding"
- android:paddingLeft="@dimen/blocked_numbers_large_padding"
+ android:paddingEnd="@dimen/blocked_numbers_large_padding"
+ android:paddingStart="@dimen/blocked_numbers_large_padding"
android:src="@drawable/ic_status_blocked_orange_40dp"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/icon"
+ android:layout_toEndOf="@id/icon"
android:paddingTop="@dimen/blocked_numbers_large_padding"
- android:paddingRight="@dimen/blocked_numbers_large_padding"
+ android:paddingEnd="@dimen/blocked_numbers_large_padding"
android:text="@string/blocked_numbers_butter_bar_title"
style="@style/BlockedNumbersTextPrimary2" />
@@ -45,11 +45,11 @@
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/icon"
+ android:layout_toEndOf="@id/icon"
android:layout_below="@id/title"
android:paddingTop="@dimen/blocked_numbers_large_padding"
android:paddingBottom="@dimen/blocked_numbers_large_padding"
- android:paddingRight="@dimen/blocked_numbers_large_padding"
+ android:paddingEnd="@dimen/blocked_numbers_large_padding"
android:text="@string/blocked_numbers_butter_bar_body"
style="@style/BlockedNumbersTextSecondary" />
@@ -57,9 +57,9 @@
android:id="@+id/reenable_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/icon"
+ android:layout_toEndOf="@id/icon"
android:layout_below="@id/description"
- android:paddingRight="@dimen/blocked_numbers_large_padding"
+ android:paddingEnd="@dimen/blocked_numbers_large_padding"
android:text="@string/blocked_numbers_butter_bar_button"
style="@style/BlockedNumbersButton"
android:background="?android:attr/selectableItemBackgroundBorderless" />
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 3fbac1f..912305b 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -30,6 +30,9 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -39,6 +42,9 @@
*/
@VisibleForTesting
public class AsyncRingtonePlayer {
+ // Maximum amount of time we will delay playing a ringtone while waiting for audio routing to
+ // be ready.
+ private static final int PLAY_DELAY_TIMEOUT_MS = 1000;
// Message codes used with the ringtone thread.
private static final int EVENT_PLAY = 1;
private static final int EVENT_STOP = 2;
@@ -49,6 +55,23 @@
/** The current ringtone. Only used by the ringtone thread. */
private Ringtone mRingtone;
+ /**
+ * Set to true if we are setting up to play or are currently playing. False if we are stopping
+ * or have stopped playing.
+ */
+ private boolean mIsPlaying = false;
+
+ /**
+ * Set to true if BT HFP is active and audio is connected.
+ */
+ private boolean mIsBtActive = false;
+
+ /**
+ * A list of pending ringing ready latches, which are used to delay the ringing command until
+ * audio paths are set and ringing is ready.
+ */
+ private final ArrayList<CountDownLatch> mPendingRingingLatches = new ArrayList<>();
+
public AsyncRingtonePlayer() {
// Empty
}
@@ -60,21 +83,81 @@
*
* @param ringtoneSupplier The {@link Ringtone} factory.
* @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration).
+ * @param isHfpDeviceConnected True if there is a HFP BT device connected, false otherwise.
*/
public void play(@NonNull Supplier<Ringtone> ringtoneSupplier,
- BiConsumer<Ringtone, Boolean> ringtoneConsumer) {
+ BiConsumer<Ringtone, Boolean> ringtoneConsumer, boolean isHfpDeviceConnected) {
Log.d(this, "Posting play.");
+ mIsPlaying = true;
SomeArgs args = SomeArgs.obtain();
args.arg1 = ringtoneSupplier;
args.arg2 = ringtoneConsumer;
args.arg3 = Log.createSubsession();
+ args.arg4 = prepareRingingReadyLatch(isHfpDeviceConnected);
postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
}
/** Stops playing the ringtone. */
public void stop() {
Log.d(this, "Posting stop.");
+ mIsPlaying = false;
postMessage(EVENT_STOP, false /* shouldCreateHandler */, null);
+ // Clear any pending ringing latches so that we do not have to wait for its timeout to pass
+ // before calling stop.
+ clearPendingRingingLatches();
+ }
+
+ /**
+ * Called when the BT HFP profile active state changes.
+ * @param isBtActive A BT device is connected and audio is active.
+ */
+ public void updateBtActiveState(boolean isBtActive) {
+ Log.i(this, "updateBtActiveState: " + isBtActive);
+ synchronized (mPendingRingingLatches) {
+ mIsBtActive = isBtActive;
+ if (isBtActive) mPendingRingingLatches.forEach(CountDownLatch::countDown);
+ }
+ }
+
+ /**
+ * Prepares a new ringing ready latch and tracks it in a list. Once the ready latch has been
+ * used, {@link #removePendingRingingReadyLatch(CountDownLatch)} must be called on this latch.
+ * @param isHfpDeviceConnected true if there is a HFP device connected.
+ * @return the newly prepared CountDownLatch
+ */
+ private CountDownLatch prepareRingingReadyLatch(boolean isHfpDeviceConnected) {
+ CountDownLatch latch = new CountDownLatch(1);
+ synchronized (mPendingRingingLatches) {
+ // We only want to delay ringing if BT is connected but not active yet.
+ boolean isDelayRequired = isHfpDeviceConnected && !mIsBtActive;
+ Log.i(this, "prepareRingingReadyLatch:"
+ + " connected=" + isHfpDeviceConnected
+ + ", BT active=" + mIsBtActive
+ + ", isDelayRequired=" + isDelayRequired);
+ if (!isDelayRequired) latch.countDown();
+ mPendingRingingLatches.add(latch);
+ }
+ return latch;
+ }
+
+ /**
+ * Remove a ringing ready latch that has been used and is no longer pending.
+ * @param l The latch to remove.
+ */
+ private void removePendingRingingReadyLatch(CountDownLatch l) {
+ synchronized (mPendingRingingLatches) {
+ mPendingRingingLatches.remove(l);
+ }
+ }
+
+ /**
+ * Count down all pending ringing ready latches and then clear the list.
+ */
+ private void clearPendingRingingLatches() {
+ synchronized (mPendingRingingLatches) {
+ mPendingRingingLatches.forEach(CountDownLatch::countDown);
+ mPendingRingingLatches.clear();
+ }
}
/**
@@ -129,6 +212,7 @@
Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1;
BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2;
Session session = (Session) args.arg3;
+ CountDownLatch ringingReadyLatch = (CountDownLatch) args.arg4;
args.recycle();
Log.continueSession(session, "ARP.hP");
@@ -136,17 +220,29 @@
// Don't bother with any of this if there is an EVENT_STOP waiting, but give the
// consumer a chance to do anything no matter what.
if (mHandler.hasMessages(EVENT_STOP)) {
+ Log.i(this, "handlePlay: skipping play early due to pending STOP");
+ removePendingRingingReadyLatch(ringingReadyLatch);
ringtoneConsumer.accept(null, /* stopped= */ true);
return;
}
Ringtone ringtone = null;
boolean hasStopped = false;
try {
+ try {
+ Log.i(this, "handlePlay: delay ring for ready signal...");
+ boolean reachedZero = ringingReadyLatch.await(PLAY_DELAY_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ Log.i(this, "handlePlay: ringing ready, timeout=" + !reachedZero);
+ } catch (InterruptedException e) {
+ Log.w(this, "handlePlay: latch exception: " + e);
+ }
ringtone = ringtoneSupplier.get();
- // Ringtone supply can be slow. Re-check for stop event.
+ // Ringtone supply can be slow or stop command could have been issued while waiting
+ // for BT to move to CONNECTED state. Re-check for stop event.
if (mHandler.hasMessages(EVENT_STOP)) {
+ Log.i(this, "handlePlay: skipping play due to pending STOP");
hasStopped = true;
- ringtone.stop(); // proactively release the ringtone.
+ if (ringtone != null) ringtone.stop(); // proactively release the ringtone.
return;
}
// setRingtone even if null - it also stops any current ringtone to be consistent
@@ -168,6 +264,7 @@
mRingtone.play();
Log.i(this, "Play ringtone, looping.");
} finally {
+ removePendingRingingReadyLatch(ringingReadyLatch);
ringtoneConsumer.accept(ringtone, hasStopped);
}
} finally {
@@ -196,11 +293,15 @@
}
}
+ /**
+ * @return true if we are currently preparing or playing a ringtone, false if we are not.
+ */
public boolean isPlaying() {
- return mRingtone != null;
+ return mIsPlaying;
}
private void setRingtone(@Nullable Ringtone ringtone) {
+ Log.i(this, "setRingtone: ringtone null=" + (ringtone == null));
// Make sure that any previously created instance of Ringtone is stopped so the MediaPlayer
// can be released, before replacing mRingtone with a new instance. This is always created
// as a looping Ringtone, so if not stopped it will keep playing on the background.
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index dd8e7e8..341d77e 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,12 +17,13 @@
package com.android.server.telecom;
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
-import static android.telecom.Call.EVENT_DISPLAY_SOS_MESSAGE;
+import static android.telephony.TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -30,6 +31,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -43,6 +45,7 @@
import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
import android.telecom.CallDiagnostics;
+import android.telecom.CallException;
import android.telecom.CallerInfo;
import android.telecom.Conference;
import android.telecom.Connection;
@@ -55,7 +58,6 @@
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
-import android.telecom.Response;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
@@ -70,9 +72,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.Flags;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.stats.CallStateChangedAtomWriter;
import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VerifyCallStateChangeTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
import java.io.IOException;
import java.text.SimpleDateFormat;
@@ -118,6 +125,24 @@
private static final char NO_DTMF_TONE = '\0';
+
+ /**
+ * Listener for CallState changes which can be leveraged by a Transaction.
+ */
+ public interface CallStateListener {
+ void onCallStateChanged(int newCallState);
+ }
+
+ public List<CallStateListener> mCallStateListeners = new ArrayList<>();
+
+ public void addCallStateListener(CallStateListener newListener) {
+ mCallStateListeners.add(newListener);
+ }
+
+ public boolean removeCallStateListener(CallStateListener newListener) {
+ return mCallStateListeners.remove(newListener);
+ }
+
/**
* Listener for events on the call.
*/
@@ -283,18 +308,25 @@
@Override
public void onCallerInfoQueryComplete(Uri handle, CallerInfo callerInfo) {
synchronized (mLock) {
- Call.this.setCallerInfo(handle, callerInfo);
+ Call call = Call.this;
+ if (call != null) {
+ call.setCallerInfo(handle, callerInfo);
+ }
}
}
@Override
public void onContactPhotoQueryComplete(Uri handle, CallerInfo callerInfo) {
synchronized (mLock) {
- Call.this.setCallerInfo(handle, callerInfo);
+ Call call = Call.this;
+ if (call != null) {
+ call.setCallerInfo(handle, callerInfo);
+ }
}
}
};
+ private final boolean mIsModifyStatePermissionGranted;
/**
* One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
*/
@@ -406,6 +438,13 @@
private int mCallerDisplayNamePresentation;
/**
+ * The remote connection service which is attempted or already connecting this call. This is set
+ * to a non-null value only when a connection manager phone account is in use. When set, this
+ * will correspond to the target phone account of the {@link Call}.
+ */
+ private ConnectionServiceWrapper mRemoteConnectionService;
+
+ /**
* The connection service which is attempted or already connecting this call.
*/
private ConnectionServiceWrapper mConnectionService;
@@ -757,6 +796,8 @@
*/
private CompletableFuture<Boolean> mDisconnectFuture;
+ private FeatureFlags mFlags;
+
/**
* Persists the specified parameters and initializes the new instance.
* @param context The context.
@@ -788,11 +829,12 @@
boolean shouldAttachToExistingConnection,
boolean isConference,
ClockProxy clockProxy,
- ToastFactory toastFactory) {
+ ToastFactory toastFactory,
+ FeatureFlags featureFlags) {
this(callId, context, callsManager, lock, repository, phoneNumberUtilsAdapter,
handle, null, gatewayInfo, connectionManagerPhoneAccountHandle,
targetPhoneAccountHandle, callDirection, shouldAttachToExistingConnection,
- isConference, clockProxy, toastFactory);
+ isConference, clockProxy, toastFactory, featureFlags);
}
@@ -812,8 +854,9 @@
boolean shouldAttachToExistingConnection,
boolean isConference,
ClockProxy clockProxy,
- ToastFactory toastFactory) {
-
+ ToastFactory toastFactory,
+ FeatureFlags featureFlags) {
+ mFlags = featureFlags;
mId = callId;
mConnectionId = callId;
mState = (isConference && callDirection != CALL_DIRECTION_INCOMING &&
@@ -844,6 +887,8 @@
mStartRingTime = 0;
mCallStateChangedAtomWriter.setExistingCallCount(callsManager.getCalls().size());
+ mIsModifyStatePermissionGranted =
+ isModifyPhoneStatePermissionGranted(getDelegatePhoneAccountHandle());
}
/**
@@ -863,6 +908,7 @@
* connection, regardless of whether it's incoming or outgoing.
* @param connectTimeMillis The connection time of the call.
* @param clockProxy
+ * @param featureFlags The telecom feature flags.
*/
Call(
String callId,
@@ -881,11 +927,13 @@
long connectTimeMillis,
long connectElapsedTimeMillis,
ClockProxy clockProxy,
- ToastFactory toastFactory) {
+ ToastFactory toastFactory,
+ FeatureFlags featureFlags) {
this(callId, context, callsManager, lock, repository,
phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
- shouldAttachToExistingConnection, isConference, clockProxy, toastFactory);
+ shouldAttachToExistingConnection, isConference, clockProxy, toastFactory,
+ featureFlags);
mConnectTimeMillis = connectTimeMillis;
mConnectElapsedTimeMillis = connectElapsedTimeMillis;
@@ -1328,6 +1376,12 @@
Log.addEvent(this, event, stringData);
}
+ if (mFlags.transactionalCsVerifier()) {
+ for (CallStateListener listener : mCallStateListeners) {
+ listener.onCallStateChanged(newState);
+ }
+ }
+
mCallStateChangedAtomWriter
.setDisconnectCause(getDisconnectCause())
.setSelfManaged(isSelfManaged())
@@ -1733,8 +1787,12 @@
accountHandle.getComponentName().getPackageName(),
mContext.getPackageManager());
// Set the associated user for the call for MT calls based on the target phone account.
- if (isIncoming() && !accountHandle.getUserHandle().equals(mAssociatedUser)) {
- setAssociatedUser(accountHandle.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFlags.associatedUserRefactorForWorkProfile(),
+ mCallsManager.getPhoneAccountRegistrar(), mCallsManager.getCurrentUserHandle(),
+ accountHandle);
+ if (isIncoming() && !associatedUser.equals(mAssociatedUser)) {
+ setAssociatedUser(associatedUser);
}
}
}
@@ -2313,11 +2371,25 @@
@VisibleForTesting
public void setConnectionService(ConnectionServiceWrapper service) {
+ setConnectionService(service, null);
+ }
+
+ @VisibleForTesting
+ public void setConnectionService(
+ ConnectionServiceWrapper service,
+ ConnectionServiceWrapper remoteService
+ ) {
Preconditions.checkNotNull(service);
clearConnectionService();
service.incrementAssociatedCallCount();
+
+ if (mFlags.updatedRcsCallCountTracking() && remoteService != null) {
+ remoteService.incrementAssociatedCallCount();
+ mRemoteConnectionService = remoteService;
+ }
+
mConnectionService = service;
mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
mConnectionService.addCall(this);
@@ -2325,10 +2397,12 @@
/**
* Perform an in-place replacement of the {@link ConnectionServiceWrapper} for this Call.
- * Removes the call from its former {@link ConnectionServiceWrapper}, ensuring that the
- * ConnectionService is NOT unbound if the call count hits zero.
- * This is used by the {@link ConnectionServiceWrapper} when handling {@link Connection} and
- * {@link Conference} additions via a ConnectionManager.
+ * Removes the call from its former {@link ConnectionServiceWrapper}, while still ensuring the
+ * former {@link ConnectionServiceWrapper} is tracked as the mRemoteConnectionService for this
+ * call so that the associatedCallCount of that {@link ConnectionServiceWrapper} is accurately
+ * tracked until it is supposed to be unbound.
+ * This method is used by the {@link ConnectionServiceWrapper} when handling {@link Connection}
+ * and {@link Conference} additions via a ConnectionManager.
* The original {@link android.telecom.ConnectionService} will directly add external calls and
* conferences to Telecom as well as the ConnectionManager, which will add to Telecom. In these
* cases since its first added to via the original CS, we want to change the CS responsible for
@@ -2341,9 +2415,18 @@
if (mConnectionService != null) {
ConnectionServiceWrapper serviceTemp = mConnectionService;
+
+ if (mFlags.updatedRcsCallCountTracking()) {
+ // Continue to track the former CS for this call so that it doesn't unbind early:
+ mRemoteConnectionService = serviceTemp;
+ }
+
mConnectionService = null;
serviceTemp.removeCall(this);
- serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/);
+
+ if (!mFlags.updatedRcsCallCountTracking()) {
+ serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/);
+ }
}
service.incrementAssociatedCallCount();
@@ -2357,6 +2440,8 @@
void clearConnectionService() {
if (mConnectionService != null) {
ConnectionServiceWrapper serviceTemp = mConnectionService;
+ ConnectionServiceWrapper remoteServiceTemp = mRemoteConnectionService;
+ mRemoteConnectionService = null;
mConnectionService = null;
serviceTemp.removeCall(this);
@@ -2367,6 +2452,10 @@
// necessary, but cleaning up mConnectionService prior to triggering an unbind is good
// to do.
decrementAssociatedCallCount(serviceTemp);
+
+ if (mFlags.updatedRcsCallCountTracking() && remoteServiceTemp != null) {
+ decrementAssociatedCallCount(remoteServiceTemp);
+ }
}
}
@@ -2385,7 +2474,7 @@
return;
}
mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
- phoneAccountRegistrar, mContext);
+ phoneAccountRegistrar, mContext, mFlags);
mCreateConnectionProcessor.process();
}
@@ -2898,11 +2987,19 @@
hold(null /* reason */);
}
+ /**
+ * This method requests the ConnectionService or TransactionalService hosting the call to put
+ * the call on hold
+ */
public void hold(String reason) {
if (mState == CallState.ACTIVE) {
if (mTransactionalService != null) {
mTransactionalService.onSetInactive(this);
} else if (mConnectionService != null) {
+ if (mFlags.transactionalCsVerifier()) {
+ awaitCallStateChangeAndMaybeDisconnectCall(CallState.ON_HOLD, isSelfManaged(),
+ "hold");
+ }
mConnectionService.hold(this);
} else {
Log.e(this, new NullPointerException(),
@@ -2913,6 +3010,27 @@
}
/**
+ * helper that can be used for any callback that requests a call state change and wants to
+ * verify the change
+ */
+ public void awaitCallStateChangeAndMaybeDisconnectCall(int targetCallState,
+ boolean shouldDisconnectUponTimeout, String callingMethod) {
+ TransactionManager tm = TransactionManager.getInstance();
+ tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager,
+ this, targetCallState, shouldDisconnectUponTimeout), new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ }
+
+ @Override
+ public void onError(CallException e) {
+ Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onError"
+ + " due to CallException=[%s]", callingMethod, e);
+ }
+ });
+ }
+
+ /**
* Releases the call from hold if it is currently active.
*/
@VisibleForTesting
@@ -3039,6 +3157,12 @@
Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE));
}
+ if (mExtras.containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL)) {
+ if (source != SOURCE_CONNECTION_SERVICE || !mIsModifyStatePermissionGranted) {
+ mExtras.remove(TelecomManager.EXTRA_DO_NOT_LOG_CALL);
+ }
+ }
+
// If the change originated from an InCallService, notify the connection service.
if (source == SOURCE_INCALL_SERVICE) {
Log.addEvent(this, LogUtils.Events.ICS_EXTRAS_CHANGED);
@@ -3053,6 +3177,15 @@
}
}
+ private boolean isModifyPhoneStatePermissionGranted(PhoneAccountHandle phoneAccountHandle) {
+ if (phoneAccountHandle == null) {
+ return false;
+ }
+ String packageName = phoneAccountHandle.getComponentName().getPackageName();
+ return PackageManager.PERMISSION_GRANTED == mContext.getPackageManager().checkPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+ }
+
/**
* Removes extras from the extras bundle associated with this {@link Call}.
*
@@ -3293,62 +3426,16 @@
*/
public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
if (mConnectionService != null || mTransactionalService != null) {
- if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
- if (targetSdkVer > Build.VERSION_CODES.P) {
- Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
- " for API > 28(P)");
- // Event-based Handover APIs are deprecated, so inform the user.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mToastFactory.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.
- if (extras == null) {
- Log.w(this, "sendCallEvent: %s event received with null extras.",
- android.telecom.Call.EVENT_REQUEST_HANDOVER);
- sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED,
- null);
- return;
- }
- Parcelable parcelable = extras.getParcelable(
- android.telecom.Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE);
- if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
- Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
- android.telecom.Call.EVENT_REQUEST_HANDOVER);
- sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED, null);
- return;
- }
- PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
- int videoState = extras.getInt(android.telecom.Call.EXTRA_HANDOVER_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY);
- Parcelable handoverExtras = extras.getParcelable(
- android.telecom.Call.EXTRA_HANDOVER_EXTRAS);
- Bundle handoverExtrasBundle = null;
- if (handoverExtras instanceof Bundle) {
- handoverExtrasBundle = (Bundle) handoverExtras;
- }
- requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle, true);
- } else {
- // Relay bluetooth call quality reports to the call diagnostic service.
- if (BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT.equals(event)
- && extras.containsKey(
- BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT)) {
- notifyBluetoothCallQualityReport(extras.getParcelable(
- BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT
- ));
- }
- Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
- sendEventToService(this, event, extras);
+ // Relay bluetooth call quality reports to the call diagnostic service.
+ if (BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT.equals(event)
+ && extras.containsKey(
+ BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT)) {
+ notifyBluetoothCallQualityReport(extras.getParcelable(
+ BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT
+ ));
}
+ Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
+ sendEventToService(this, event, extras);
} else {
Log.e(this, new NullPointerException(),
"sendCallEvent failed due to null CS callId=%s", getId());
@@ -3663,7 +3750,8 @@
}
String newName = callerInfo.getName();
- boolean contactNameChanged = mCallerInfo == null || !mCallerInfo.getName().equals(newName);
+ boolean contactNameChanged = mCallerInfo == null ||
+ !Objects.equals(mCallerInfo.getName(), newName);
mCallerInfo = callerInfo;
Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
@@ -3689,7 +3777,7 @@
Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
mCannedSmsResponsesLoadingStarted = true;
mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
- new Response<Void, List<String>>() {
+ new CallsManager.Response<Void, List<String>>() {
@Override
public void onResult(Void request, List<String>... result) {
if (result.length > 0) {
@@ -4032,7 +4120,7 @@
* @param associatedUser
*/
public void setAssociatedUser(UserHandle associatedUser) {
- Log.i(this, "Setting associated user for call");
+ Log.i(this, "Setting associated user for call: %s", associatedUser);
Preconditions.checkNotNull(associatedUser);
mAssociatedUser = associatedUser;
}
@@ -4182,8 +4270,8 @@
l.onReceivedCallQualityReport(this, callQuality);
}
} else {
- if (event.equals(EVENT_DISPLAY_SOS_MESSAGE) && !isEmergencyCall()) {
- Log.w(this, "onConnectionEvent: EVENT_DISPLAY_SOS_MESSAGE is sent "
+ if (event.equals(EVENT_DISPLAY_EMERGENCY_MESSAGE) && !isEmergencyCall()) {
+ Log.w(this, "onConnectionEvent: EVENT_DISPLAY_EMERGENCY_MESSAGE is sent "
+ "without an emergency call");
return;
}
@@ -4504,6 +4592,7 @@
}
public void setStartFailCause(CallFailureCause cause) {
+ Log.i(this, "setStartFailCause: cause = %s; callId = %s", cause, this.getId());
mCallStateChangedAtomWriter.setStartFailCause(cause);
}
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
new file mode 100644
index 0000000..5fc2414
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.flags.Flags;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+
+/**
+ * Helper class used to keep track of the requested communication device within Telecom for audio
+ * use cases. Handles the set/clear communication use case logic for all audio routes (speaker, BT,
+ * headset, and earpiece). For BT devices, this handles switches between hearing aids, SCO, and LE
+ * audio (also takes into account switching between multiple LE audio devices).
+ */
+public class CallAudioCommunicationDeviceTracker {
+
+ // Use -1 indicates device is not set for any communication use case
+ private static final int sAUDIO_DEVICE_TYPE_INVALID = -1;
+ // Possible bluetooth audio device types
+ private static final Set<Integer> sBT_AUDIO_DEVICE_TYPES = Set.of(
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO
+ );
+ private AudioManager mAudioManager;
+ private BluetoothRouteManager mBluetoothRouteManager;
+ private int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
+ // Keep track of the locally requested BT audio device if set
+ private String mBtAudioDevice = null;
+ private final Semaphore mLock = new Semaphore(1);
+
+ public CallAudioCommunicationDeviceTracker(Context context) {
+ mAudioManager = context.getSystemService(AudioManager.class);
+ }
+
+ public void setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager) {
+ mBluetoothRouteManager = bluetoothRouteManager;
+ }
+
+ public boolean isAudioDeviceSetForType(int audioDeviceType) {
+ return mAudioDeviceType == audioDeviceType;
+ }
+
+ public int getCurrentLocallyRequestedCommunicationDevice() {
+ return mAudioDeviceType;
+ }
+
+ @VisibleForTesting
+ public void setTestCommunicationDevice(int audioDeviceType) {
+ mAudioDeviceType = audioDeviceType;
+ }
+
+ public void clearBtCommunicationDevice() {
+ if (mBtAudioDevice == null) {
+ Log.i(this, "No bluetooth device was set for communication that can be cleared.");
+ return;
+ }
+ // If mBtAudioDevice is set, we know a BT audio device was set for communication so
+ // mAudioDeviceType corresponds to a BT device type (e.g. hearing aid, SCO, LE).
+ clearCommunicationDevice(mAudioDeviceType);
+ }
+
+ /*
+ * Sets the communication device for the passed in audio device type, if it's available for
+ * communication use cases. Tries to clear any communication device which was previously
+ * requested for communication before setting the new device.
+ * @param audioDeviceTypes The supported audio device types for the device.
+ * @param btDevice The bluetooth device to connect to (only used for switching between multiple
+ * LE audio devices).
+ * @return {@code true} if the device was set for communication, {@code false} if the device
+ * wasn't set.
+ */
+ public boolean setCommunicationDevice(int audioDeviceType,
+ BluetoothDevice btDevice) {
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.tryAcquire();
+ }
+ // There is only one audio device type associated with each type of BT device.
+ boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+ Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
+ audioDeviceType, isBtDevice, btDevice);
+
+ // Account for switching between multiple LE audio devices.
+ boolean handleLeAudioDeviceSwitch = btDevice != null
+ && !btDevice.getAddress().equals(mBtAudioDevice);
+ if ((audioDeviceType == mAudioDeviceType
+ || isUsbHeadsetType(audioDeviceType, mAudioDeviceType))
+ && !handleLeAudioDeviceSwitch) {
+ Log.i(this, "Communication device is already set for this audio type");
+ return false;
+ }
+
+ AudioDeviceInfo activeDevice = null;
+ List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
+ if (devices.size() == 0) {
+ Log.w(this, "No communication devices available");
+ return false;
+ }
+
+ for (AudioDeviceInfo device : devices) {
+ Log.i(this, "Available device type: " + device.getType());
+ // Ensure that we do not select the same BT LE audio device for communication.
+ if ((audioDeviceType == device.getType()
+ || isUsbHeadsetType(audioDeviceType, device.getType()))
+ && !device.getAddress().equals(mBtAudioDevice)) {
+ activeDevice = device;
+ break;
+ }
+ }
+
+ if (activeDevice == null) {
+ Log.i(this, "No active device of type(s) %s available",
+ audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
+ ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ AudioDeviceInfo.TYPE_USB_HEADSET)
+ : audioDeviceType);
+ return false;
+ }
+
+ // Force clear previous communication device, if one was set, before setting the new device.
+ if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) {
+ clearCommunicationDevice(mAudioDeviceType);
+ }
+
+ // Turn activeDevice ON.
+ boolean result = mAudioManager.setCommunicationDevice(activeDevice);
+ if (!result) {
+ Log.w(this, "Could not set active device");
+ } else {
+ Log.i(this, "Active device set");
+ mAudioDeviceType = activeDevice.getType();
+ if (isBtDevice) {
+ mBtAudioDevice = activeDevice.getAddress();
+ if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+ mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
+ }
+ } else if (Flags.communicationDeviceProtectedByLock()) {
+ // Clear BT device if it's still stored. Handles race condition for when a non-BT
+ // device is set for communication shortly after a BT (LE) device is set for
+ // communication but the selection hasn't been cleared yet.
+ mBtAudioDevice = null;
+ }
+ }
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.release();
+ }
+ return result;
+ }
+
+ /*
+ * Clears the communication device for the passed in audio device types, given that the device
+ * has previously been set for communication.
+ * @param audioDeviceTypes The supported audio device types for the device.
+ */
+ public void clearCommunicationDevice(int audioDeviceType) {
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.tryAcquire();
+ }
+ // There is only one audio device type associated with each type of BT device.
+ boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+ Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
+ audioDeviceType, isBtDevice);
+
+ if (audioDeviceType != mAudioDeviceType
+ && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType)) {
+ Log.i(this, "Unable to clear communication device of type(s), %s. "
+ + "Device does not correspond to the locally requested device type.",
+ audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
+ ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ AudioDeviceInfo.TYPE_USB_HEADSET)
+ : audioDeviceType
+ );
+ return;
+ }
+
+ if (mAudioManager == null) {
+ Log.i(this, "clearCommunicationDevice: mAudioManager is null");
+ return;
+ }
+
+ // Clear device and reset locally saved device type.
+ mAudioManager.clearCommunicationDevice();
+ mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
+
+ if (isBtDevice && mBtAudioDevice != null) {
+ // Signal that BT audio was lost for device.
+ mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
+ mBtAudioDevice = null;
+ }
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.release();
+ }
+ }
+
+ private boolean isUsbHeadsetType(int audioDeviceType, int sourceType) {
+ return audioDeviceType != AudioDeviceInfo.TYPE_WIRED_HEADSET
+ ? false : sourceType == AudioDeviceInfo.TYPE_USB_HEADSET;
+ }
+}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index ff76b9e..96bf2c6 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -23,7 +23,6 @@
import android.os.UserHandle;
import android.telecom.CallAudioState;
import android.telecom.Log;
-import android.telecom.PhoneAccount;
import android.telecom.VideoProfile;
import android.util.SparseArray;
@@ -31,11 +30,14 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+
public class CallAudioManager extends CallsManagerListenerBase {
@@ -52,7 +54,7 @@
private final Set<Call> mCalls;
private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
- private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ private final CallAudioRouteAdapter mCallAudioRouteAdapter;
private final CallAudioModeStateMachine mCallAudioModeStateMachine;
private final BluetoothStateReceiver mBluetoothStateReceiver;
private final CallsManager mCallsManager;
@@ -60,6 +62,7 @@
private final Ringer mRinger;
private final RingbackPlayer mRingbackPlayer;
private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
+ private final FeatureFlags mFeatureFlags;
private Call mStreamingCall;
private Call mForegroundCall;
@@ -67,14 +70,15 @@
private boolean mIsDisconnectedTonePlaying = false;
private InCallTonePlayer mHoldTonePlayer;
- public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
+ public CallAudioManager(CallAudioRouteAdapter callAudioRouteAdapter,
CallsManager callsManager,
CallAudioModeStateMachine callAudioModeStateMachine,
InCallTonePlayer.Factory playerFactory,
Ringer ringer,
RingbackPlayer ringbackPlayer,
BluetoothStateReceiver bluetoothStateReceiver,
- DtmfLocalTonePlayer dtmfLocalTonePlayer) {
+ DtmfLocalTonePlayer dtmfLocalTonePlayer,
+ FeatureFlags featureFlags) {
mActiveDialingOrConnectingCalls = new LinkedHashSet<>(1);
mRingingCalls = new LinkedHashSet<>(1);
mHoldingCalls = new LinkedHashSet<>(1);
@@ -92,7 +96,7 @@
put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls);
}};
- mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+ mCallAudioRouteAdapter = callAudioRouteAdapter;
mCallAudioModeStateMachine = callAudioModeStateMachine;
mCallsManager = callsManager;
mPlayerFactory = playerFactory;
@@ -100,10 +104,11 @@
mRingbackPlayer = ringbackPlayer;
mBluetoothStateReceiver = bluetoothStateReceiver;
mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
+ mFeatureFlags = featureFlags;
mPlayerFactory.setCallAudioManager(this);
mCallAudioModeStateMachine.setCallAudioManager(this);
- mCallAudioRouteStateMachine.setCallAudioManager(this);
+ mCallAudioRouteAdapter.setCallAudioManager(this);
}
@Override
@@ -116,7 +121,7 @@
// State did not change, so no need to do anything.
return;
}
- Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
+ Log.i(this, "onCallStateChanged: Call state changed for TC@%s: %s -> %s", call.getId(),
CallState.toString(oldState), CallState.toString(newState));
removeCallFromAllBins(call);
@@ -220,7 +225,7 @@
// When pulling a video call, automatically enable the speakerphone.
Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
call.getId());
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.SWITCH_SPEAKER);
}
}
@@ -302,7 +307,7 @@
VideoProfile.isReceptionEnabled(newVideoState);
if (isUpgradeRequest) {
- mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
+ mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
}
}
@@ -311,7 +316,7 @@
// We only play tones for foreground calls.
return;
}
- mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RTT_REQUEST).startTone();
+ mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_RTT_REQUEST).startTone();
}
/**
@@ -324,7 +329,7 @@
*/
@Override
public void onHoldToneRequested(Call call) {
- maybePlayHoldTone();
+ maybePlayHoldTone(call);
}
@Override
@@ -372,7 +377,7 @@
@Override
public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
ConnectionServiceWrapper newCs) {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
}
@@ -390,13 +395,13 @@
Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
" to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
VideoProfile.videoStateToString(newVideoState));
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.SWITCH_SPEAKER);
}
}
public CallAudioState getCallAudioState() {
- return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+ return mCallAudioRouteAdapter.getCurrentCallAudioState();
}
public Call getPossiblyHeldForegroundCall() {
@@ -417,7 +422,7 @@
Log.v(this, "ignoring toggleMute for emergency call");
return;
}
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.TOGGLE_MUTE);
}
@@ -437,7 +442,7 @@
Log.v(this, "ignoring mute for emergency call");
}
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(shouldMute
? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
}
@@ -453,23 +458,23 @@
Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
switch (route) {
case CallAudioState.ROUTE_BLUETOOTH:
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
return;
case CallAudioState.ROUTE_SPEAKER:
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
return;
case CallAudioState.ROUTE_WIRED_HEADSET:
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
return;
case CallAudioState.ROUTE_EARPIECE:
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
return;
case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
return;
@@ -484,7 +489,7 @@
*/
void switchBaseline() {
Log.i(this, "switchBaseline");
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
}
@@ -528,7 +533,7 @@
synchronized (mCallsManager.getLock()) {
Call localForegroundCall = mForegroundCall;
boolean result = mRinger.startRinging(localForegroundCall,
- mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+ mCallAudioRouteAdapter.isHfpDeviceAvailable());
if (result) {
localForegroundCall.setStartRingTime();
}
@@ -561,7 +566,7 @@
@VisibleForTesting
public void setCallAudioRouteFocusState(int focusState) {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
}
@@ -571,8 +576,8 @@
}
@VisibleForTesting
- public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
- return mCallAudioRouteStateMachine;
+ public CallAudioRouteAdapter getCallAudioRouteAdapter() {
+ return mCallAudioRouteAdapter;
}
@VisibleForTesting
@@ -609,9 +614,9 @@
mCallAudioModeStateMachine.dump(pw);
pw.decreaseIndent();
- pw.println("CallAudioRouteStateMachine:");
+ pw.println("mCallAudioRouteAdapter:");
pw.increaseIndent();
- mCallAudioRouteStateMachine.dump(pw);
+ mCallAudioRouteAdapter.dump(pw);
pw.decreaseIndent();
pw.println("BluetoothDeviceManager:");
@@ -623,7 +628,7 @@
}
@VisibleForTesting
- public void setIsTonePlaying(boolean isTonePlaying) {
+ public void setIsTonePlaying(Call call, boolean isTonePlaying) {
Log.i(this, "setIsTonePlaying; isTonePlaying=%b", isTonePlaying);
mIsTonePlaying = isTonePlaying;
mCallAudioModeStateMachine.sendMessageWithArgs(
@@ -632,7 +637,7 @@
makeArgsForModeStateMachine());
if (!isTonePlaying && mIsDisconnectedTonePlaying) {
- mCallsManager.onDisconnectedTonePlaying(false);
+ mCallsManager.onDisconnectedTonePlaying(call, false);
mIsDisconnectedTonePlaying = false;
}
}
@@ -761,6 +766,7 @@
private void updateForegroundCall() {
Call oldForegroundCall = mForegroundCall;
+
if (mActiveDialingOrConnectingCalls.size() > 0) {
// Give preference for connecting calls over active/dialing for foreground-ness.
Call possibleConnectingCall = null;
@@ -769,8 +775,27 @@
possibleConnectingCall = call;
}
}
- mForegroundCall = possibleConnectingCall == null ?
- mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
+ if (mFeatureFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+ // Prefer a connecting call
+ if (possibleConnectingCall != null) {
+ mForegroundCall = possibleConnectingCall;
+ } else {
+ // Next, prefer an active or dialing call which is not in the process of being
+ // disconnected.
+ mForegroundCall = mActiveDialingOrConnectingCalls
+ .stream()
+ .filter(c -> (c.getState() == CallState.ACTIVE
+ || c.getState() == CallState.DIALING)
+ && !c.isLocallyDisconnecting())
+ .findFirst()
+ // If we can't find one, then just fall back to the first one.
+ .orElse(mActiveDialingOrConnectingCalls.iterator().next());
+ }
+ } else {
+ // Legacy (buggy) behavior.
+ mForegroundCall = possibleConnectingCall == null ?
+ mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
+ }
} else if (mRingingCalls.size() > 0) {
mForegroundCall = mRingingCalls.iterator().next();
} else if (mHoldingCalls.size() > 0) {
@@ -778,12 +803,27 @@
} else {
mForegroundCall = null;
}
-
+ Log.i(this, "updateForegroundCall; oldFg=%s, newFg=%s, aDC=%s, ring=%s, hold=%s",
+ (oldForegroundCall == null ? "none" : oldForegroundCall.getId()),
+ (mForegroundCall == null ? "none" : mForegroundCall.getId()),
+ mActiveDialingOrConnectingCalls.stream().map(c -> c.getId()).collect(
+ Collectors.joining(",")),
+ mRingingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(",")),
+ mHoldingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(","))
+ );
if (mForegroundCall != oldForegroundCall) {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+ if (mForegroundCall != null
+ && mFeatureFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+ // Ensure the voip audio mode for the new foreground call is taken into account.
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
+ makeArgsForModeStateMachine());
+ }
mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
- maybePlayHoldTone();
+ maybePlayHoldTone(oldForegroundCall);
}
}
@@ -886,9 +926,9 @@
Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
- boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
+ boolean didToneStart = mPlayerFactory.createPlayer(call, toneToPlay).startTone();
if (didToneStart) {
- mCallsManager.onDisconnectedTonePlaying(true);
+ mCallsManager.onDisconnectedTonePlaying(call, true);
mIsDisconnectedTonePlaying = true;
}
}
@@ -908,10 +948,11 @@
/**
* Determines if a hold tone should be played and then starts or stops it accordingly.
*/
- private void maybePlayHoldTone() {
+ private void maybePlayHoldTone(Call call) {
if (shouldPlayHoldTone()) {
if (mHoldTonePlayer == null) {
- mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ mHoldTonePlayer = mPlayerFactory.createPlayer(call,
+ InCallTonePlayer.TONE_CALL_WAITING);
mHoldTonePlayer.startTone();
}
} else {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 9ad9094..6420f2e 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Looper;
import android.os.Message;
@@ -29,6 +31,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.server.telecom.flags.FeatureFlags;
public class CallAudioModeStateMachine extends StateMachine {
/**
@@ -38,11 +41,29 @@
private LocalLog mLocalLog = new LocalLog(20);
public static class Factory {
public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
- AudioManager am) {
- return new CallAudioModeStateMachine(systemStateHelper, am);
+ AudioManager am, FeatureFlags featureFlags,
+ CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker) {
+ return new CallAudioModeStateMachine(systemStateHelper, am,
+ featureFlags, callAudioCommunicationDeviceTracker);
}
}
+ private static final AudioAttributes RING_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setLegacyStreamType(AudioManager.STREAM_RING)
+ .build();
+ public static final AudioFocusRequest RING_AUDIO_FOCUS_REQUEST = new AudioFocusRequest
+ .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+ .setAudioAttributes(RING_AUDIO_ATTRIBUTES).build();
+
+ private static final AudioAttributes CALL_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
+ .build();
+ public static final AudioFocusRequest CALL_AUDIO_FOCUS_REQUEST = new AudioFocusRequest
+ .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+ .setAudioAttributes(CALL_AUDIO_ATTRIBUTES).build();
+
public static class MessageArgs {
public boolean hasActiveOrDialingCalls;
public boolean hasRingingCalls;
@@ -212,6 +233,8 @@
public static final String STREAMING_STATE_NAME = StreamingFocusState.class.getSimpleName();
public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
+ private AudioFocusRequest mCurrentAudioFocusRequest = null;
+
private class BaseState extends State {
@Override
public boolean processMessage(Message msg) {
@@ -256,8 +279,23 @@
Log.i(LOG_TAG, "Audio focus entering UNFOCUSED state");
mLocalLog.log("Enter UNFOCUSED");
if (mIsInitialized) {
- mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ // Clear any communication device that was requested previously.
+ // Todo: Remove once clearCommunicationDeviceAfterAudioOpsComplete is
+ // completely rolled out.
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ && !mFeatureFlags.clearCommunicationDeviceAfterAudioOpsComplete()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(mCommunicationDeviceTracker
+ .getCurrentLocallyRequestedCommunicationDevice());
+ }
+ if (mFeatureFlags.setAudioModeBeforeAbandonFocus()) {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ mCallAudioManager.setCallAudioRouteFocusState(
+ CallAudioRouteStateMachine.NO_FOCUS);
+ } else {
+ mCallAudioManager.setCallAudioRouteFocusState(
+ CallAudioRouteStateMachine.NO_FOCUS);
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ }
mLocalLog.log("Mode MODE_NORMAL");
mMostRecentMode = AudioManager.MODE_NORMAL;
// Don't release focus here -- wait until we get a signal that any other audio
@@ -310,7 +348,20 @@
return HANDLED;
case AUDIO_OPERATIONS_COMPLETE:
Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
- mAudioManager.abandonAudioFocusForCall();
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ if (mCurrentAudioFocusRequest != null) {
+ mAudioManager.abandonAudioFocusRequest(mCurrentAudioFocusRequest);
+ mCurrentAudioFocusRequest = null;
+ }
+ } else {
+ mAudioManager.abandonAudioFocusForCall();
+ }
+ // Clear requested communication device after the call ends.
+ if (mFeatureFlags.clearCommunicationDeviceAfterAudioOpsComplete()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ mCommunicationDeviceTracker
+ .getCurrentLocallyRequestedCommunicationDevice());
+ }
return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
@@ -381,7 +432,14 @@
return HANDLED;
case AUDIO_OPERATIONS_COMPLETE:
Log.i(LOG_TAG, "Abandoning audio focus: now AUDIO_PROCESSING");
- mAudioManager.abandonAudioFocusForCall();
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ if (mCurrentAudioFocusRequest != null) {
+ mAudioManager.abandonAudioFocusRequest(mCurrentAudioFocusRequest);
+ mCurrentAudioFocusRequest = null;
+ }
+ } else {
+ mAudioManager.abandonAudioFocusForCall();
+ }
return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
@@ -406,8 +464,13 @@
}
if (mCallAudioManager.startRinging()) {
- mAudioManager.requestAudioFocusForCall(
- AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ mCurrentAudioFocusRequest = RING_AUDIO_FOCUS_REQUEST;
+ mAudioManager.requestAudioFocus(RING_AUDIO_FOCUS_REQUEST);
+ } else {
+ mAudioManager.requestAudioFocusForCall(
+ AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ }
// Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
// this trips up the audio system.
if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
@@ -504,8 +567,13 @@
public void enter() {
Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
mLocalLog.log("Enter SIM_CALL");
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
+ mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
+ } else {
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ }
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mLocalLog.log("Mode MODE_IN_CALL");
mMostRecentMode = AudioManager.MODE_IN_CALL;
@@ -587,8 +655,13 @@
public void enter() {
Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
mLocalLog.log("Enter VOIP_CALL");
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
+ mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
+ } else {
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ }
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mLocalLog.log("Mode MODE_IN_COMMUNICATION");
mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
@@ -670,12 +743,12 @@
mAudioManager.setMode(AudioManager.MODE_COMMUNICATION_REDIRECT);
mMostRecentMode = AudioManager.MODE_NORMAL;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
- mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
+ mCallAudioManager.getCallAudioRouteAdapter().sendMessageWithSessionInfo(
CallAudioRouteStateMachine.STREAMING_FORCE_ENABLED);
}
private void preExit() {
- mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
+ mCallAudioManager.getCallAudioRouteAdapter().sendMessageWithSessionInfo(
CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
}
@@ -742,8 +815,13 @@
public void enter() {
Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
mLocalLog.log("Enter TONE/HOLDING");
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
+ mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
+ } else {
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ }
mAudioManager.setMode(mMostRecentMode);
mLocalLog.log("Mode " + mMostRecentMode);
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
@@ -815,16 +893,21 @@
private final AudioManager mAudioManager;
private final SystemStateHelper mSystemStateHelper;
private CallAudioManager mCallAudioManager;
+ private FeatureFlags mFeatureFlags;
+ private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
private int mMostRecentMode;
private boolean mIsInitialized = false;
public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
- AudioManager audioManager) {
+ AudioManager audioManager, FeatureFlags featureFlags,
+ CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker) {
super(CallAudioModeStateMachine.class.getSimpleName());
mAudioManager = audioManager;
mSystemStateHelper = systemStateHelper;
mMostRecentMode = AudioManager.MODE_NORMAL;
+ mFeatureFlags = featureFlags;
+ mCommunicationDeviceTracker = callAudioCommunicationDeviceTracker;
createStates();
}
@@ -833,11 +916,14 @@
* Used for testing
*/
public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
- AudioManager audioManager, Looper looper) {
+ AudioManager audioManager, Looper looper, FeatureFlags featureFlags,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
super(CallAudioModeStateMachine.class.getSimpleName(), looper);
mAudioManager = audioManager;
mSystemStateHelper = systemStateHelper;
mMostRecentMode = AudioManager.MODE_NORMAL;
+ mFeatureFlags = featureFlags;
+ mCommunicationDeviceTracker = communicationDeviceTracker;
createStates();
}
diff --git a/src/com/android/server/telecom/CallAudioRouteAdapter.java b/src/com/android/server/telecom/CallAudioRouteAdapter.java
new file mode 100644
index 0000000..7f7b43c
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRouteAdapter.java
@@ -0,0 +1,19 @@
+package com.android.server.telecom;
+
+import android.os.Handler;
+import android.telecom.CallAudioState;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+public interface CallAudioRouteAdapter {
+ void initialize();
+ void sendMessageWithSessionInfo(int message);
+ void sendMessageWithSessionInfo(int message, int arg);
+ void sendMessageWithSessionInfo(int message, int arg, String data);
+ void sendMessage(int message, Runnable r);
+ void setCallAudioManager(CallAudioManager callAudioManager);
+ CallAudioState getCurrentCallAudioState();
+ boolean isHfpDeviceAvailable();
+ Handler getAdapterHandler();
+ void dump(IndentingPrintWriter pw);
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
new file mode 100644
index 0000000..f8c49bb
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -0,0 +1,64 @@
+package com.android.server.telecom;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telecom.CallAudioState;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+public class CallAudioRouteController implements CallAudioRouteAdapter {
+ private Handler mHandler;
+
+ public CallAudioRouteController() {
+ HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ }
+ @Override
+ public void initialize() {
+ }
+
+ @Override
+ public void sendMessageWithSessionInfo(int message) {
+ }
+
+ @Override
+ public void sendMessageWithSessionInfo(int message, int arg) {
+
+ }
+
+ @Override
+ public void sendMessageWithSessionInfo(int message, int arg, String data) {
+
+ }
+
+ @Override
+ public void sendMessage(int message, Runnable r) {
+
+ }
+
+ @Override
+ public void setCallAudioManager(CallAudioManager callAudioManager) {
+ }
+
+ @Override
+ public CallAudioState getCurrentCallAudioState() {
+ return null;
+ }
+
+ @Override
+ public boolean isHfpDeviceAvailable() {
+ return false;
+ }
+
+ @Override
+ public Handler getAdapterHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter pw) {
+
+ }
+}
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index d96f953..8a87c22 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -25,16 +25,19 @@
public class CallAudioRoutePeripheralAdapter implements WiredHeadsetManager.Listener,
DockManager.Listener, BluetoothRouteManager.BluetoothStateListener {
- private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ private final CallAudioRouteAdapter mCallAudioAdapter;
private final BluetoothRouteManager mBluetoothRouteManager;
+ private final AsyncRingtonePlayer mRingtonePlayer;
public CallAudioRoutePeripheralAdapter(
- CallAudioRouteStateMachine callAudioRouteStateMachine,
+ CallAudioRouteAdapter callAudioRouteAdapter,
BluetoothRouteManager bluetoothManager,
WiredHeadsetManager wiredHeadsetManager,
- DockManager dockManager) {
- mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+ DockManager dockManager,
+ AsyncRingtonePlayer ringtonePlayer) {
+ mCallAudioAdapter = callAudioRouteAdapter;
mBluetoothRouteManager = bluetoothManager;
+ mRingtonePlayer = ringtonePlayer;
mBluetoothRouteManager.setListener(this);
wiredHeadsetManager.addListener(this);
@@ -57,37 +60,47 @@
@Override
public void onBluetoothDeviceListChanged() {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
}
@Override
public void onBluetoothActiveDevicePresent() {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
}
@Override
public void onBluetoothActiveDeviceGone() {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
}
@Override
public void onBluetoothAudioConnected() {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mRingtonePlayer.updateBtActiveState(true);
+ mCallAudioAdapter.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ }
+
+ @Override
+ public void onBluetoothAudioConnecting() {
+ mRingtonePlayer.updateBtActiveState(false);
+ // Pretend like audio is connected when communicating w/ CARSM.
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
}
@Override
public void onBluetoothAudioDisconnected() {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mRingtonePlayer.updateBtActiveState(false);
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
}
@Override
public void onUnexpectedBluetoothStateChange() {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
}
@@ -98,17 +111,17 @@
@Override
public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
if (!oldIsPluggedIn && newIsPluggedIn) {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
} else if (oldIsPluggedIn && !newIsPluggedIn){
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
}
}
@Override
public void onDockChanged(boolean isDocked) {
- mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ mCallAudioAdapter.sendMessageWithSessionInfo(
isDocked ? CallAudioRouteStateMachine.CONNECT_DOCK
: CallAudioRouteStateMachine.DISCONNECT_DOCK
);
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 4a03726..c0bb50e 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -28,6 +28,7 @@
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.Binder;
+import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -44,6 +45,7 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.Collection;
import java.util.HashMap;
@@ -72,7 +74,7 @@
* from a wired headset
* mIsMuted: a boolean indicating whether the audio is muted
*/
-public class CallAudioRouteStateMachine extends StateMachine {
+public class CallAudioRouteStateMachine extends StateMachine implements CallAudioRouteAdapter {
public static class Factory {
public CallAudioRouteStateMachine create(
@@ -83,7 +85,9 @@
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
int earpieceControl,
- Executor asyncTaskExecutor) {
+ Executor asyncTaskExecutor,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
@@ -91,7 +95,9 @@
statusBarNotifier,
audioServiceFactory,
earpieceControl,
- asyncTaskExecutor);
+ asyncTaskExecutor,
+ communicationDeviceTracker,
+ featureFlags);
}
}
/** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -371,6 +377,10 @@
public void enter() {
super.enter();
setSpeakerphoneOn(false);
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, null);
+ }
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
mAvailableRoutes, null,
mBluetoothRouteManager.getConnectedDevices());
@@ -401,6 +411,10 @@
case SWITCH_BLUETOOTH:
case USER_SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ }
if (mAudioFocusType == ACTIVE_FOCUS
|| mBluetoothRouteManager.isInbandRingingEnabled()) {
String address = (msg.obj instanceof SomeArgs) ?
@@ -417,6 +431,10 @@
case SWITCH_HEADSET:
case USER_SWITCH_HEADSET:
if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ }
transitionTo(mActiveHeadsetRoute);
} else {
Log.w(this, "Ignoring switch to headset command. Not available.");
@@ -426,6 +444,10 @@
// fall through; we want to switch to speaker mode when docked and in a call.
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ }
setSpeakerphoneOn(true);
// fall through
case SPEAKER_ON:
@@ -579,6 +601,10 @@
public void enter() {
super.enter();
setSpeakerphoneOn(false);
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET, null);
+ }
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
setSystemAudioState(newState, true);
@@ -600,6 +626,10 @@
case SWITCH_EARPIECE:
case USER_SWITCH_EARPIECE:
if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ }
transitionTo(mActiveEarpieceRoute);
} else {
Log.w(this, "Ignoring switch to earpiece command. Not available.");
@@ -615,6 +645,10 @@
|| mBluetoothRouteManager.isInbandRingingEnabled()) {
String address = (msg.obj instanceof SomeArgs) ?
(String) ((SomeArgs) msg.obj).arg2 : null;
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ }
// Omit transition to ActiveBluetoothRoute until actual connection.
setBluetoothOn(address);
} else {
@@ -631,6 +665,10 @@
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ }
setSpeakerphoneOn(true);
// fall through
case SPEAKER_ON:
@@ -793,6 +831,17 @@
public void enter() {
super.enter();
setSpeakerphoneOn(false);
+ // Try arbitrarily connecting to BT audio if we haven't already. This handles
+ // the edge case of when the audio route is in a quiescent route while in-call and
+ // the BT connection fails to be set. Previously, the logic was to setBluetoothOn in
+ // ACTIVE_FOCUS but the route would still remain in a quiescent route, so instead we
+ // should be transitioning directly into the active route.
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ setBluetoothOn(null);
+ }
+ if (mFeatureFlags.updateRouteMaskWhenBtConnected()) {
+ mAvailableRoutes |= ROUTE_BLUETOOTH;
+ }
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
mBluetoothRouteManager.getConnectedDevices());
@@ -893,8 +942,13 @@
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
// Only disconnect audio here instead of routing away from BT entirely.
- mBluetoothRouteManager.disconnectAudio();
- reinitialize();
+ if (mFeatureFlags.transitRouteBeforeAudioDisconnectBt()) {
+ transitionTo(mQuiescentBluetoothRoute);
+ mBluetoothRouteManager.disconnectAudio();
+ } else {
+ mBluetoothRouteManager.disconnectAudio();
+ reinitialize();
+ }
mCallAudioManager.notifyAudioOperationsComplete();
} else if (msg.arg1 == RINGING_FOCUS
&& !mBluetoothRouteManager.isInbandRingingEnabled()) {
@@ -1019,6 +1073,9 @@
public void enter() {
super.enter();
mHasUserExplicitlyLeftBluetooth = false;
+ if (mFeatureFlags.resetMuteWhenEnteringQuiescentBtRoute()) {
+ setMuteOn(false);
+ }
updateInternalCallAudioState();
}
@@ -1065,7 +1122,13 @@
return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == ACTIVE_FOCUS) {
- setBluetoothOn(null);
+ // It is possible that the connection to BT will fail while in-call, in
+ // which case, we want to transition into the active route.
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ transitionTo(mActiveBluetoothRoute);
+ } else {
+ setBluetoothOn(null);
+ }
} else if (msg.arg1 == RINGING_FOCUS) {
if (mBluetoothRouteManager.isInbandRingingEnabled()) {
setBluetoothOn(null);
@@ -1215,7 +1278,13 @@
// Expected, since we just transitioned here
return HANDLED;
case SPEAKER_OFF:
- sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+ // Check if we already requested to connect to other devices and just waiting
+ // for their response. In some cases, this SPEAKER_OFF message may come in
+ // before the response, we can just ignore the message here to not re-evaluate
+ // the baseline route incorrectly
+ if (!mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
+ sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+ }
return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
@@ -1514,6 +1583,8 @@
private CallAudioState mLastKnownCallAudioState;
private CallAudioManager mCallAudioManager;
+ private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+ private FeatureFlags mFeatureFlags;
public CallAudioRouteStateMachine(
Context context,
@@ -1523,7 +1594,9 @@
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
int earpieceControl,
- Executor asyncTaskExecutor) {
+ Executor asyncTaskExecutor,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
super(NAME);
mContext = context;
mCallsManager = callsManager;
@@ -1534,6 +1607,8 @@
mAudioServiceFactory = audioServiceFactory;
mLock = callsManager.getLock();
mAsyncTaskExecutor = asyncTaskExecutor;
+ mCommunicationDeviceTracker = communicationDeviceTracker;
+ mFeatureFlags = featureFlags;
createStates(earpieceControl);
}
@@ -1545,7 +1620,9 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl, Looper looper, Executor asyncTaskExecutor) {
+ int earpieceControl, Looper looper, Executor asyncTaskExecutor,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
super(NAME, looper);
mContext = context;
mCallsManager = callsManager;
@@ -1556,7 +1633,8 @@
mAudioServiceFactory = audioServiceFactory;
mLock = callsManager.getLock();
mAsyncTaskExecutor = asyncTaskExecutor;
-
+ mCommunicationDeviceTracker = communicationDeviceTracker;
+ mFeatureFlags = featureFlags;
createStates(earpieceControl);
}
@@ -1673,6 +1751,11 @@
sendMessage(message, arg, 0, args);
}
+ @Override
+ public void sendMessage(int message, Runnable r) {
+ super.sendMessage(message, r);
+ }
+
/**
* This is for state-independent changes in audio route (i.e. muting or runnables)
* @param msg that couldn't be handled.
@@ -1702,9 +1785,19 @@
}
return;
case UPDATE_SYSTEM_AUDIO_ROUTE:
- updateInternalCallAudioState();
- updateRouteForForegroundCall();
- resendSystemAudioState();
+ if (mFeatureFlags.availableRoutesNeverUpdatedAfterSetSystemAudioState()) {
+ // Ensure available routes is updated.
+ updateRouteForForegroundCall();
+ // Ensure current audio state gets updated to take this into account.
+ updateInternalCallAudioState();
+ // Either resend the current audio state as it stands, or update to reflect any
+ // changes put into place based on mAvailableRoutes
+ setSystemAudioState(mCurrentCallAudioState, true);
+ } else {
+ updateInternalCallAudioState();
+ updateRouteForForegroundCall();
+ resendSystemAudioState();
+ }
return;
case RUN_RUNNABLE:
java.lang.Runnable r = (java.lang.Runnable) msg.obj;
@@ -1729,7 +1822,7 @@
}
public void dumpPendingMessages(IndentingPrintWriter pw) {
- getHandler().getLooper().dump(pw::println, "");
+ getAdapterHandler().getLooper().dump(pw::println, "");
}
public boolean isHfpDeviceAvailable() {
@@ -1741,31 +1834,19 @@
final boolean hasAnyCalls = mCallsManager.hasAnyCalls();
// These APIs are all via two-way binder calls so can potentially block Telecom. Since none
// of this has to happen in the Telecom lock we'll offload it to the async executor.
-
- AudioDeviceInfo speakerDevice = null;
- for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
- if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- speakerDevice = info;
- break;
- }
- }
boolean speakerOn = false;
- if (speakerDevice != null && on) {
- boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
- if (result) {
- speakerOn = true;
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ if (on) {
+ speakerOn = mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
+ } else {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
}
} else {
- AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
- if (curDevice != null
- && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- mAudioManager.clearCommunicationDevice();
- }
+ speakerOn = processLegacySpeakerCommunicationDevice(on);
}
- final boolean isSpeakerOn = speakerOn;
- mAsyncTaskExecutor.execute(() -> {
- mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && isSpeakerOn);
- });
+ mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
}
private void setBluetoothOn(String address) {
@@ -1863,6 +1944,11 @@
setSystemAudioState(mLastKnownCallAudioState, true);
}
+ @VisibleForTesting
+ public CallAudioState getLastKnownCallAudioState() {
+ return mLastKnownCallAudioState;
+ }
+
private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
synchronized (mLock) {
Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
@@ -1980,6 +2066,58 @@
return false;
}
+ private boolean isWatchActiveOrOnlyWatchesAvailable() {
+ if (!mFeatureFlags.ignoreAutoRouteToWatchDevice()) {
+ Log.i(this, "isWatchActiveOrOnlyWatchesAvailable: Flag is disabled.");
+ return false;
+ }
+
+ boolean containsWatchDevice = false;
+ boolean containsNonWatchDevice = false;
+ Collection<BluetoothDevice> connectedBtDevices =
+ mBluetoothRouteManager.getConnectedDevices();
+
+ for (BluetoothDevice connectedDevice: connectedBtDevices) {
+ if (mBluetoothRouteManager.isWatch(connectedDevice)) {
+ containsWatchDevice = true;
+ } else {
+ containsNonWatchDevice = true;
+ }
+ }
+
+ // Don't ignore switch if watch is already the active device.
+ boolean isActiveDeviceWatch = mBluetoothRouteManager.isWatch(
+ mBluetoothRouteManager.getBluetoothAudioConnectedDevice());
+ Log.i(this, "isWatchActiveOrOnlyWatchesAvailable: contains watch: %s, contains "
+ + "non-wearable device: %s, is active device a watch: %s.",
+ containsWatchDevice, containsNonWatchDevice, isActiveDeviceWatch);
+ return containsWatchDevice && !containsNonWatchDevice && !isActiveDeviceWatch;
+ }
+
+ private boolean processLegacySpeakerCommunicationDevice(boolean on) {
+ AudioDeviceInfo speakerDevice = null;
+ for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+ if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ speakerDevice = info;
+ break;
+ }
+ }
+ boolean speakerOn = false;
+ if (speakerDevice != null && on) {
+ boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+ if (result) {
+ speakerOn = true;
+ }
+ } else {
+ AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+ if (curDevice != null
+ && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ mAudioManager.clearCommunicationDevice();
+ }
+ }
+ return speakerOn;
+ }
+
private int calculateBaselineRouteMessage(boolean isExplicitUserRequest,
boolean includeBluetooth) {
boolean isSkipEarpiece = false;
@@ -1992,7 +2130,7 @@
}
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0
&& !mHasUserExplicitlyLeftBluetooth
- && includeBluetooth) {
+ && includeBluetooth && !isWatchActiveOrOnlyWatchesAvailable()) {
return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH;
} else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) {
return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
@@ -2048,4 +2186,8 @@
return base;
}
+
+ public Handler getAdapterHandler() {
+ return getHandler();
+ }
}
diff --git a/src/com/android/server/telecom/CallDiagnosticServiceController.java b/src/com/android/server/telecom/CallDiagnosticServiceController.java
index 6c7ee38..1077f0d 100644
--- a/src/com/android/server/telecom/CallDiagnosticServiceController.java
+++ b/src/com/android/server/telecom/CallDiagnosticServiceController.java
@@ -522,7 +522,7 @@
callId, messageId, message);
if (mPlayerFactory != null) {
// Play that tone!
- mPlayerFactory.createPlayer(InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION)
+ mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION)
.startTone();
}
call.displayDiagnosticMessage(messageId, message);
diff --git a/src/com/android/server/telecom/CallEndpointController.java b/src/com/android/server/telecom/CallEndpointController.java
index 7e11b47..4738cd4 100644
--- a/src/com/android/server/telecom/CallEndpointController.java
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -87,7 +87,7 @@
}
public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
- Log.d(this, "requestCallEndpointChange %s", endpoint);
+ Log.i(this, "requestCallEndpointChange %s", endpoint);
int route = mTypeToRouteMap.get(endpoint.getEndpointType());
String bluetoothAddress = getBluetoothAddress(endpoint);
@@ -99,7 +99,6 @@
}
if (isCurrentEndpointRequestedEndpoint(route, bluetoothAddress)) {
- Log.d(this, "requestCallEndpointChange: requested endpoint is already active");
callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
return;
}
@@ -130,21 +129,27 @@
return false;
}
CallAudioState currentAudioState = mCallsManager.getCallAudioManager().getCallAudioState();
- // requested non-bt endpoint is already active
- if (requestedRoute != CallAudioState.ROUTE_BLUETOOTH &&
- requestedRoute == currentAudioState.getRoute()) {
- return true;
- }
- // requested bt endpoint is already active
- if (requestedRoute == CallAudioState.ROUTE_BLUETOOTH &&
- currentAudioState.getActiveBluetoothDevice() != null &&
- requestedAddress.equals(
- currentAudioState.getActiveBluetoothDevice().getAddress())) {
- return true;
+ if (requestedRoute == currentAudioState.getRoute()) {
+ if (requestedRoute != CallAudioState.ROUTE_BLUETOOTH) {
+ // The audio route (earpiece, speaker, etc.) is already active
+ // and Telecom can ignore the spam request!
+ Log.i(this, "iCERE: user requested a non-BT route that is already active");
+ return true;
+ } else if (hasSameBluetoothAddress(currentAudioState, requestedAddress)) {
+ // if the requested (BT route, device) is active, ignore the request...
+ Log.i(this, "iCERE: user requested a BT endpoint that is already active");
+ return true;
+ }
}
return false;
}
+ public boolean hasSameBluetoothAddress(CallAudioState audioState, String requestedAddress) {
+ boolean hasActiveBtDevice = audioState.getActiveBluetoothDevice() != null;
+ return hasActiveBtDevice && requestedAddress.equals(
+ audioState.getActiveBluetoothDevice().getAddress());
+ }
+
private Bundle getErrorResult(int result) {
String message;
int resultCode;
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 7953324..062c872 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -1,6 +1,7 @@
package com.android.server.telecom;
import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.flags.FeatureFlags;
import android.content.Context;
import android.content.Intent;
@@ -32,7 +33,7 @@
public class CallIntentProcessor {
public interface Adapter {
void processOutgoingCallIntent(Context context, CallsManager callsManager,
- Intent intent, String callingPackage);
+ Intent intent, String callingPackage, FeatureFlags featureFlags);
void processIncomingCallIntent(CallsManager callsManager, Intent intent);
void processUnknownCallIntent(CallsManager callsManager, Intent intent);
}
@@ -45,9 +46,9 @@
@Override
public void processOutgoingCallIntent(Context context, CallsManager callsManager,
- Intent intent, String callingPackage) {
+ Intent intent, String callingPackage, FeatureFlags featureFlags) {
CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
- callingPackage, mDefaultDialerCache);
+ callingPackage, mDefaultDialerCache, featureFlags);
}
@Override
@@ -73,12 +74,14 @@
private final Context mContext;
private final CallsManager mCallsManager;
private final DefaultDialerCache mDefaultDialerCache;
+ private final FeatureFlags mFeatureFlags;
public CallIntentProcessor(Context context, CallsManager callsManager,
- DefaultDialerCache defaultDialerCache) {
+ DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags) {
this.mContext = context;
this.mCallsManager = callsManager;
this.mDefaultDialerCache = defaultDialerCache;
+ this.mFeatureFlags = featureFlags;
}
public void processIntent(Intent intent, String callingPackage) {
@@ -90,7 +93,7 @@
processUnknownCallIntent(mCallsManager, intent);
} else {
processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
- mDefaultDialerCache);
+ mDefaultDialerCache, mFeatureFlags);
}
Trace.endSection();
}
@@ -107,7 +110,8 @@
CallsManager callsManager,
Intent intent,
String callingPackage,
- DefaultDialerCache defaultDialerCache) {
+ DefaultDialerCache defaultDialerCache,
+ FeatureFlags featureFlags) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
@@ -182,10 +186,9 @@
boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
initiatingUser.getIdentifier());
-
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
- isPrivilegedDialer, defaultDialerCache, new MmiUtils());
+ isPrivilegedDialer, defaultDialerCache, new MmiUtils(), featureFlags);
// If the broadcaster comes back with an immediate error, disconnect and show a dialog.
NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 3005656..6a9977d 100644
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -19,17 +19,24 @@
import static android.provider.CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
import android.location.Country;
import android.location.CountryDetector;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.UserHandle;
import android.os.PersistableBundle;
+import android.os.UserManager;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.telecom.Connection;
@@ -42,13 +49,18 @@
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.stream.Stream;
/**
@@ -68,16 +80,19 @@
*/
private static class AddCallArgs {
public AddCallArgs(Context context, CallLog.AddCallParams params,
- @Nullable LogCallCompletedListener logCallCompletedListener) {
+ @Nullable LogCallCompletedListener logCallCompletedListener,
+ @NonNull Call call) {
this.context = context;
this.params = params;
this.logCallCompletedListener = logCallCompletedListener;
+ this.call = call;
}
// Since the members are accessed directly, we don't use the
// mXxxx notation.
public final Context context;
public final CallLog.AddCallParams params;
+ public final Call call;
@Nullable
public final LogCallCompletedListener logCallCompletedListener;
}
@@ -88,29 +103,41 @@
// TODO: come up with a better way to indicate in a android.telecom.DisconnectCause that
// a conference was merged successfully
private static final String REASON_IMS_MERGED_SUCCESSFULLY = "IMS_MERGED_SUCCESSFULLY";
+ private static final UUID LOG_CALL_FAILED_ANOMALY_ID =
+ UUID.fromString("d9b38771-ff36-417b-8723-2363a870c702");
+ private static final String LOG_CALL_FAILED_ANOMALY_DESC =
+ "Based on the current user, Telecom detected failure to record a call to the call log.";
private final Context mContext;
private final CarrierConfigManager mCarrierConfigManager;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
+ private AnomalyReporterAdapter mAnomalyReporterAdapter;
private static final String ACTION_CALLS_TABLE_ADD_ENTRY =
- "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
+ "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
private static final String PERMISSION_PROCESS_CALLLOG_INFO =
- "android.permission.PROCESS_CALLLOG_INFO";
+ "android.permission.PROCESS_CALLLOG_INFO";
private static final String CALL_TYPE = "callType";
private static final String CALL_DURATION = "duration";
- private Object mLock;
+ private final Object mLock = new Object();
+ private Country mCurrentCountry;
private String mCurrentCountryIso;
+ private HandlerExecutor mCountryCodeExecutor;
+
+ private final FeatureFlags mFeatureFlags;
public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
- MissedCallNotifier missedCallNotifier) {
+ MissedCallNotifier missedCallNotifier, AnomalyReporterAdapter anomalyReporterAdapter,
+ FeatureFlags featureFlags) {
mContext = context;
mCarrierConfigManager = (CarrierConfigManager) mContext
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
mPhoneAccountRegistrar = phoneAccountRegistrar;
mMissedCallNotifier = missedCallNotifier;
- mLock = new Object();
+ mAnomalyReporterAdapter = anomalyReporterAdapter;
+ mCountryCodeExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
+ mFeatureFlags = featureFlags;
}
@Override
@@ -149,9 +176,10 @@
* Call was NOT in the "choose account" phase when disconnected
* Call is NOT a conference call which had children (unless it was remotely hosted).
* Call is NOT a child call from a conference which was remotely hosted.
+ * Call has NOT indicated it should be skipped for logging in its extras
* Call is NOT simulating a single party conference.
* Call was NOT explicitly canceled, except for disconnecting from a conference.
- * Call is NOT an external call
+ * Call is NOT an external call or an external call on watch.
* Call is NOT disconnected because of merging into a conference.
* Call is NOT a self-managed call OR call is a self-managed call which has indicated it
* should be logged in its PhoneAccount
@@ -180,6 +208,11 @@
return false;
}
+ if (mFeatureFlags.telecomSkipLogBasedOnExtra() && call.getExtras() != null
+ && call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL)) {
+ return false;
+ }
+
// A child call of a conference which was remotely hosted; these didn't originate on this
// device and should not be logged.
if (call.getParentCall() != null && call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
@@ -200,8 +233,10 @@
& Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE)
== Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE;
}
- // An external call
- if (call.isExternalCall()) {
+ // An external and non-watch call
+ if (call.isExternalCall() && (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH)
+ || !mFeatureFlags.telecomLogExternalWearableCalls())) {
return false;
}
@@ -240,8 +275,13 @@
logCall(call, type, new LogCallCompletedListener() {
@Override
public void onLogCompleted(@Nullable Uri uri) {
- mMissedCallNotifier.showMissedCallNotification(
- new MissedCallNotifier.CallInfo(call));
+ if (mFeatureFlags.addCallUriForMissedCalls()){
+ mMissedCallNotifier.showMissedCallNotification(
+ new MissedCallNotifier.CallInfo(call), uri);
+ } else {
+ mMissedCallNotifier.showMissedCallNotification(
+ new MissedCallNotifier.CallInfo(call), /* uri= */ null);
+ }
}
}, result);
} else {
@@ -263,7 +303,7 @@
* {@link android.provider.CallLog.Calls#BLOCKED_TYPE}.
*/
void logCall(Call call, int callLogType,
- @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
+ @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
CallLog.AddCallParams.AddCallParametersBuilder paramBuilder =
new CallLog.AddCallParams.AddCallParametersBuilder();
@@ -385,7 +425,7 @@
okayToLogCall(accountHandle, logNumber, call.isEmergencyCall());
if (okayToLog) {
AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
- logCallCompletedListener);
+ logCallCompletedListener, call);
Log.addEvent(call, LogUtils.Events.LOG_CALL, "number=" + Log.piiHandle(logNumber)
+ ",postDial=" + Log.piiHandle(call.getPostDialDigits()) + ",pres="
+ call.getHandlePresentation());
@@ -516,8 +556,25 @@
AddCallArgs c = callList[i];
mListeners[i] = c.logCallCompletedListener;
try {
- // May block.
+ Pair<Integer, Integer> startStats = getCallLogStats(c.call);
+ Log.i(TAG, "LogCall; about to log callId=%s, "
+ + "startCount=%d, startMaxId=%d",
+ c.call.getId(), startStats.first, startStats.second);
+
result[i] = Calls.addCall(c.context, c.params);
+ Pair<Integer, Integer> endStats = getCallLogStats(c.call);
+ Log.i(TAG, "LogCall; logged callId=%s, uri=%s, "
+ + "endCount=%d, endMaxId=%s",
+ c.call.getId(), result, endStats.first, endStats.second);
+ if ((endStats.second - startStats.second) <= 0) {
+ // No call was added or even worse we lost a call in the log. Trigger an
+ // anomaly report. Note: it technically possible that an app modified the
+ // call log while we were writing to it here; that is pretty unlikely, and
+ // the goal here is to try and identify potential anomalous conditions with
+ // logging calls.
+ mAnomalyReporterAdapter.reportAnomaly(LOG_CALL_FAILED_ANOMALY_ID,
+ LOG_CALL_FAILED_ANOMALY_DESC);
+ }
} catch (Exception e) {
// This is very rare but may happen in legitimate cases.
// E.g. If the phone is encrypted and thus write request fails, it may cause
@@ -526,8 +583,10 @@
//
// We don't want to crash the whole process just because of that, so just log
// it instead.
- Log.e(TAG, e, "Exception raised during adding CallLog entry.");
+ Log.e(TAG, e, "LogCall: Exception raised adding callId=%s", c.call.getId());
result[i] = null;
+ mAnomalyReporterAdapter.reportAnomaly(LOG_CALL_FAILED_ANOMALY_ID,
+ LOG_CALL_FAILED_ANOMALY_DESC);
}
}
return result;
@@ -567,7 +626,7 @@
return Locale.getDefault().getCountry();
}
- return country.getCountryIso();
+ return country.getCountryCode();
}
/**
@@ -578,28 +637,84 @@
public String getCountryIso() {
synchronized (mLock) {
if (mCurrentCountryIso == null) {
- Log.i(TAG, "Country cache is null. Detecting Country and Setting Cache...");
+ // Moving this into the constructor will pose issues if the service is not yet set
+ // up, causing a RemoteException to be thrown. Note that the callback is only
+ // registered if the country iso cache is null (so in an ideal setting, this should
+ // only require a one-time configuration).
final CountryDetector countryDetector =
(CountryDetector) mContext.getSystemService(Context.COUNTRY_DETECTOR);
- Country country = null;
if (countryDetector != null) {
- country = countryDetector.detectCountry();
-
- countryDetector.addCountryListener((newCountry) -> {
- Log.startSession("CLM.oCD");
- try {
- synchronized (mLock) {
- Log.i(TAG, "Country ISO changed. Retrieving new ISO...");
- mCurrentCountryIso = getCountryIsoFromCountry(newCountry);
- }
- } finally {
- Log.endSession();
- }
- }, Looper.getMainLooper());
+ countryDetector.registerCountryDetectorCallback(
+ mCountryCodeExecutor, this::countryCodeConsumer);
}
- mCurrentCountryIso = getCountryIsoFromCountry(country);
+ mCurrentCountryIso = getCountryIsoFromCountry(mCurrentCountry);
}
return mCurrentCountryIso;
}
}
+
+ /** Consumer to receive the country code if it changes. */
+ private void countryCodeConsumer(Country newCountry) {
+ Log.startSession("CLM.cCC");
+ try {
+ Log.i(TAG, "Country ISO changed. Retrieving new ISO...");
+ synchronized (mLock) {
+ mCurrentCountry = newCountry;
+ mCurrentCountryIso = getCountryIsoFromCountry(newCountry);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Returns a pair containing the number of rows in the call log, as well as the maximum call log
+ * ID. There is a limit of 500 entries in the call log for a phone account, so once we hit 500
+ * we can reasonably expect that number to not change before and after logging a call.
+ * We determine the maximum ID in the call log since this is a way we can objectively check if
+ * the provider did record a call log entry or not. Ideally there should be more call log
+ * entries after logging than before, and certainly not less.
+ * @return pair with number of rows in the call log and max id.
+ */
+ private Pair<Integer, Integer> getCallLogStats(@NonNull Call call) {
+ try {
+ // Ensure we query the call log based on the current user.
+ final Context currentUserContext = mContext.createContextAsUser(
+ call.getAssociatedUser(), /* flags= */ 0);
+ final ContentResolver currentUserResolver = currentUserContext.getContentResolver();
+ final UserManager userManager = currentUserContext.getSystemService(UserManager.class);
+ final int currentUserId = userManager.getProcessUserId();
+
+ // Use shadow provider based on current user unlock state.
+ Uri providerUri;
+ if (userManager.isUserUnlocked(currentUserId)) {
+ providerUri = Calls.CONTENT_URI;
+ } else {
+ providerUri = Calls.SHADOW_CONTENT_URI;
+ }
+ int maxCallId = -1;
+ int numFound;
+ try (Cursor countCursor = currentUserResolver.query(providerUri,
+ new String[]{Calls._ID},
+ null,
+ null,
+ Calls._ID + " DESC")) {
+ numFound = countCursor.getCount();
+ if (numFound > 0) {
+ countCursor.moveToFirst();
+ maxCallId = countCursor.getInt(0);
+ }
+ }
+ return new Pair<>(numFound, maxCallId);
+ } catch (Exception e) {
+ // Oh jeepers, we crashed getting the call count.
+ Log.e(TAG, e, "getCountOfCallLogRows: failed");
+ return new Pair<>(-1, -1);
+ }
+ }
+
+ @VisibleForTesting
+ public void setAnomalyReporterAdapter(AnomalyReporterAdapter anomalyReporterAdapter){
+ mAnomalyReporterAdapter = anomalyReporterAdapter;
+ }
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 77570c3..cc5a15d 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -73,6 +73,7 @@
import android.os.Process;
import android.os.ResultReceiver;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.SystemVibrator;
import android.os.Trace;
import android.os.UserHandle;
@@ -129,9 +130,11 @@
import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
import com.android.server.telecom.callfiltering.DndCallFilter;
import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraphProvider;
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
import com.android.server.telecom.components.ErrorDialogActivity;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
@@ -140,8 +143,8 @@
import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.TransactionManager;
import com.android.server.telecom.voip.VoipCallMonitor;
+import com.android.server.telecom.voip.TransactionManager;
import java.util.ArrayList;
import java.util.Arrays;
@@ -210,7 +213,7 @@
void onHoldToneRequested(Call call);
void onExternalCallChanged(Call call, boolean isExternalCall);
void onCallStreamingStateChanged(Call call, boolean isStreaming);
- void onDisconnectedTonePlaying(boolean isTonePlaying);
+ void onDisconnectedTonePlaying(Call call, boolean isTonePlaying);
void onConnectionTimeChanged(Call call);
void onConferenceStateChanged(Call call, boolean isConference);
void onCdmaConferenceSwap(Call call);
@@ -222,6 +225,29 @@
void performAction();
}
+ /**
+ * @hide
+ */
+ public interface Response<IN, OUT> {
+
+ /**
+ * Provide a set of results.
+ *
+ * @param request The original request.
+ * @param result The results.
+ */
+ void onResult(IN request, OUT... result);
+
+ /**
+ * Indicates the inability to provide results.
+ *
+ * @param request The original request.
+ * @param code An integer code indicating the reason for failure.
+ * @param msg A message explaining the reason for failure.
+ */
+ void onError(IN request, int code, String msg);
+ }
+
private static final String TAG = "CallsManager";
/**
@@ -286,15 +312,14 @@
public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
"Exception thrown while retrieving list of potential phone accounts when placing an "
+ "emergency call.";
- public static final UUID EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID =
- UUID.fromString("f9a916c8-8d61-4550-9ad3-11c2e84f6364");
- public static final String EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG =
- "An emergency call was disconnected after the connection was created but before the "
- + "call was successfully added to CallsManager.";
public static final UUID EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID =
UUID.fromString("2e994acb-1997-4345-8bf3-bad04303de26");
public static final String EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG =
"An emergency call was aborted since there were no available phone accounts.";
+ public static final UUID TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_UUID =
+ UUID.fromString("0a86157c-50ca-11ee-be56-0242ac120002");
+ public static final String TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG =
+ "Telephony has a default MO acct but Telecom prompted user for MO";
private static final int[] OUTGOING_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -463,6 +488,9 @@
private final TransactionManager mTransactionManager;
private final UserManager mUserManager;
private final CallStreamingNotification mCallStreamingNotification;
+ private final FeatureFlags mFeatureFlags;
+
+ private final IncomingCallFilterGraphProvider mIncomingCallFilterGraphProvider;
private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -572,10 +600,14 @@
CallAnomalyWatchdog callAnomalyWatchdog,
Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
Executor asyncTaskExecutor,
+ Executor asyncCallAudioTaskExecutor,
BlockedNumbersAdapter blockedNumbersAdapter,
TransactionManager transactionManager,
EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger,
- CallStreamingNotification callStreamingNotification) {
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ CallStreamingNotification callStreamingNotification,
+ FeatureFlags featureFlags,
+ IncomingCallFilterGraphProvider incomingCallFilterGraphProvider) {
mContext = context;
mLock = lock;
@@ -594,28 +626,36 @@
mEmergencyCallHelper = emergencyCallHelper;
mCallerInfoLookupHelper = callerInfoLookupHelper;
mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
+ mIncomingCallFilterGraphProvider = incomingCallFilterGraphProvider;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
- CallAudioRouteStateMachine callAudioRouteStateMachine =
- callAudioRouteStateMachineFactory.create(
- context,
- this,
- bluetoothManager,
- wiredHeadsetManager,
- statusBarNotifier,
- audioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
- asyncTaskExecutor
- );
- callAudioRouteStateMachine.initialize();
+ CallAudioRouteAdapter callAudioRouteAdapter;
+ if (!featureFlags.useRefactoredAudioRouteSwitching()) {
+ callAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
+ context,
+ this,
+ bluetoothManager,
+ wiredHeadsetManager,
+ statusBarNotifier,
+ audioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+ asyncCallAudioTaskExecutor,
+ communicationDeviceTracker,
+ featureFlags
+ );
+ } else {
+ callAudioRouteAdapter = new CallAudioRouteController();
+ }
+ callAudioRouteAdapter.initialize();
CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
new CallAudioRoutePeripheralAdapter(
- callAudioRouteStateMachine,
+ callAudioRouteAdapter,
bluetoothManager,
wiredHeadsetManager,
- mDockManager);
+ mDockManager,
+ asyncRingtonePlayer);
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
(resourceId, attributes) ->
@@ -639,37 +679,45 @@
ringtoneFactory, systemVibrator,
new Ringer.VibrationEffectProxy(), mInCallController,
mContext.getSystemService(NotificationManager.class),
- accessibilityManagerAdapter);
+ accessibilityManagerAdapter, featureFlags);
mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
mTimeoutsAdapter, mLock);
- mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
+ mCallAudioManager = new CallAudioManager(callAudioRouteAdapter,
this, callAudioModeStateMachineFactory.create(systemStateHelper,
- (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)),
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE),
+ featureFlags, communicationDeviceTracker),
playerFactory, mRinger, new RingbackPlayer(playerFactory),
- bluetoothStateReceiver, mDtmfLocalTonePlayer);
+ bluetoothStateReceiver, mDtmfLocalTonePlayer, featureFlags);
mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(mRequester);
mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
mTtyManager = new TtyManager(context, mWiredHeadsetManager);
mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
- mCallLogManager = new CallLogManager(context, phoneAccountRegistrar, mMissedCallNotifier);
+ mCallLogManager = new CallLogManager(context, phoneAccountRegistrar, mMissedCallNotifier,
+ mAnomalyReporter, featureFlags);
mConnectionServiceRepository =
new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
mClockProxy = clockProxy;
mToastFactory = toastFactory;
mRoleManagerAdapter = roleManagerAdapter;
+ mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
mTransactionManager = transactionManager;
mBlockedNumbersAdapter = blockedNumbersAdapter;
mCallStreamingController = new CallStreamingController(mContext, mLock);
- mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
mCallStreamingNotification = callStreamingNotification;
+ mFeatureFlags = featureFlags;
+ if (mFeatureFlags.useImprovedListenerOrder()) {
+ mListeners.add(mInCallController);
+ }
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
- mListeners.add(mInCallController);
+ if (!mFeatureFlags.useImprovedListenerOrder()) {
+ mListeners.add(mInCallController);
+ }
mListeners.add(mCallEndpointController);
mListeners.add(mCallDiagnosticServiceController);
mListeners.add(mCallAudioManager);
@@ -744,11 +792,14 @@
@Override
@VisibleForTesting
public void onSuccessfulOutgoingCall(Call call, int callState) {
- Log.v(this, "onSuccessfulOutgoingCall, %s", call);
+ Log.v(this, "onSuccessfulOutgoingCall, call=[%s], state=[%d]", call, callState);
call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp(
call.getAssociatedUser()));
- setCallState(call, callState, "successful outgoing call");
+ if (!mFeatureFlags.fixAudioFlickerForOutgoingCalls()) {
+ setCallState(call, callState, "successful outgoing call");
+ }
+
if (!mCalls.contains(call)) {
// Call was not added previously in startOutgoingCall due to it being a potential MMI
// code, so add it now.
@@ -760,7 +811,18 @@
listener.onConnectionServiceChanged(call, null, call.getConnectionService());
}
- markCallAsDialing(call);
+ if (mFeatureFlags.fixAudioFlickerForOutgoingCalls()) {
+ // Allow the ConnectionService to start the call in the active state. This case is
+ // helpful for conference calls or meetings that can skip the dialing stage.
+ if (callState == CallState.ACTIVE) {
+ setCallState(call, callState, "skipping the dialing state and setting active");
+ } else {
+ markCallAsDialing(call);
+ }
+ }
+ else{
+ markCallAsDialing(call);
+ }
}
@Override
@@ -779,11 +841,12 @@
? new Bundle()
: phoneAccount.getExtras();
TelephonyManager telephonyManager = getTelephonyManager();
+ boolean performDndFilter = mFeatureFlags.skipFilterPhoneAccountPerformDndFilter();
if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE) ||
incomingCall.hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL) ||
telephonyManager.isInEmergencySmsMode() ||
incomingCall.isSelfManaged() ||
- extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+ (!performDndFilter && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING))) {
Log.i(this, "Skipping call filtering for %s (ecm=%b, "
+ "networkIdentifiedEmergencyCall = %b, emergencySmsMode = %b, "
+ "selfMgd=%b, skipExtra=%b)",
@@ -801,25 +864,40 @@
.build(), false);
incomingCall.setIsUsingCallFiltering(false);
return;
+ } else if (performDndFilter && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+ IncomingCallFilterGraph graph = setupDndFilterOnlyGraph(incomingCall);
+ graph.performFiltering();
+ return;
}
IncomingCallFilterGraph graph = setUpCallFilterGraph(incomingCall);
graph.performFiltering();
}
+ private IncomingCallFilterGraph setupDndFilterOnlyGraph(Call incomingHfpCall) {
+ incomingHfpCall.setIsUsingCallFiltering(true);
+ DndCallFilter dndCallFilter = new DndCallFilter(incomingHfpCall, mRinger);
+ IncomingCallFilterGraph graph = mIncomingCallFilterGraphProvider.createGraph(
+ incomingHfpCall,
+ this::onCallFilteringComplete, mContext, mTimeoutsAdapter, mLock);
+ graph.addFilter(dndCallFilter);
+ mGraphHandlerThreads.add(graph.getHandlerThread());
+ return graph;
+ }
+
private IncomingCallFilterGraph setUpCallFilterGraph(Call incomingCall) {
+ TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
incomingCall.setIsUsingCallFiltering(true);
String carrierPackageName = getCarrierPackageName();
UserHandle userHandle = incomingCall.getAssociatedUser();
- String defaultDialerPackageName = TelecomManager.from(mContext).
- getDefaultDialerPackage(userHandle);
+ String defaultDialerPackageName = telecomManager.getDefaultDialerPackage(userHandle);
String userChosenPackageName = getRoleManagerAdapter().
getDefaultCallScreeningApp(userHandle);
AppLabelProxy appLabelProxy = packageName -> AppLabelProxy.Util.getAppLabel(
mContext.getPackageManager(), packageName);
ParcelableCallUtils.Converter converter = new ParcelableCallUtils.Converter();
- IncomingCallFilterGraph graph = new IncomingCallFilterGraph(incomingCall,
+ IncomingCallFilterGraph graph = mIncomingCallFilterGraphProvider.createGraph(incomingCall,
this::onCallFilteringComplete, mContext, mTimeoutsAdapter, mLock);
DirectToVoicemailFilter voicemailFilter = new DirectToVoicemailFilter(incomingCall,
mCallerInfoLookupHelper);
@@ -986,7 +1064,7 @@
if (result.shouldShowNotification) {
Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
mMissedCallNotifier.showMissedCallNotification(
- new MissedCallNotifier.CallInfo(incomingCall));
+ new MissedCallNotifier.CallInfo(incomingCall), /* uri= */ null);
}
}
}
@@ -1244,9 +1322,7 @@
@Override
public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
Bundle extras, boolean isLegacy) {
- if (isLegacy) {
- requestHandoverViaEvents(call, handoverTo, videoState, extras);
- } else {
+ if (!isLegacy) {
requestHandover(call, handoverTo, videoState, extras);
}
}
@@ -1299,7 +1375,7 @@
return mCallAudioManager;
}
- InCallController getInCallController() {
+ public InCallController getInCallController() {
return mInCallController;
}
@@ -1379,8 +1455,11 @@
}
@VisibleForTesting
- public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
- mAnomalyReporter = mAnomalyReporterAdapter;
+ public void setAnomalyReporterAdapter(AnomalyReporterAdapter anomalyReporterAdapter){
+ mAnomalyReporter = anomalyReporterAdapter;
+ if (mCallLogManager != null) {
+ mCallLogManager.setAnomalyReporterAdapter(anomalyReporterAdapter);
+ }
}
void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
@@ -1425,7 +1504,8 @@
false /* forceAttachToExistingConnection */,
isConference, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
// 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
@@ -1452,7 +1532,10 @@
}
}
// Incoming address was set via EXTRA_INCOMING_CALL_ADDRESS above.
- call.setAssociatedUser(phoneAccountHandle.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.associatedUserRefactorForWorkProfile(),
+ getPhoneAccountRegistrar(), getCurrentUserHandle(), phoneAccountHandle);
+ call.setAssociatedUser(associatedUser);
}
if (phoneAccount != null) {
@@ -1572,15 +1655,19 @@
// Check if the target phone account is possibly in ECBM.
call.setIsInECBM(getEmergencyCallHelper()
.isLastOutgoingEmergencyCallPAH(call.getTargetPhoneAccount()));
- // If the phone account user profile is paused or the call isn't visible to the secondary/
- // guest user, reject the non-emergency incoming call. When the current user is the admin,
- // we need to allow the calls to go through if the work profile isn't paused. We should
- // always allow emergency calls and also allow non-emergency calls when ECBM is active for
- // the phone account.
- if ((mUserManager.isQuietModeEnabled(call.getAssociatedUser())
- || (!mUserManager.isUserAdmin(mCurrentUserHandle.getIdentifier())
- && !isCallVisibleForUser(call, mCurrentUserHandle)))
- && !call.isEmergencyCall() && !call.isInECBM()) {
+
+ // Check if call is visible to the current user.
+ boolean isCallHiddenFromProfile = !isCallVisibleForUser(call, mCurrentUserHandle);
+ // For admins, we should check if the work profile is paused in order to reject
+ // the call.
+ if (mUserManager.isUserAdmin(mCurrentUserHandle.getIdentifier())) {
+ isCallHiddenFromProfile &= mUserManager.isQuietModeEnabled(
+ call.getAssociatedUser());
+ }
+
+ // We should always allow emergency calls and also allow non-emergency calls when ECBM
+ // is active for the phone account.
+ if (isCallHiddenFromProfile && !call.isEmergencyCall() && !call.isInECBM()) {
Log.d(TAG, "Rejecting non-emergency call because the owner %s is not running.",
phoneAccountHandle.getUserHandle());
call.setMissedReason(USER_MISSED_NOT_RUNNING);
@@ -1653,11 +1740,15 @@
true /* forceAttachToExistingConnection */,
false, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
call.initAnalytics();
// For unknown calls, base the associated user off of the target phone account handle.
- call.setAssociatedUser(phoneAccountHandle.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.associatedUserRefactorForWorkProfile(),
+ getPhoneAccountRegistrar(), getCurrentUserHandle(), phoneAccountHandle);
+ call.setAssociatedUser(associatedUser);
setIntentExtrasAndStartTime(call, extras);
call.addListener(this);
notifyStartCreateConnection(call);
@@ -1771,7 +1862,8 @@
false /* forceAttachToExistingConnection */,
isConference, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
@@ -2047,7 +2139,7 @@
+ " available accounts.");
showErrorMessage(R.string.cant_call_due_to_no_supported_service);
mListeners.forEach(l -> l.onCreateConnectionFailed(callToPlace));
- if (callToPlace.isEmergencyCall()){
+ if (callToPlace.isEmergencyCall()) {
mAnomalyReporter.reportAnomaly(
EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
@@ -2060,6 +2152,21 @@
return CompletableFuture.completedFuture(Pair.create(callToPlace,
accountSuggestions.get(0).getPhoneAccountHandle()));
}
+
+ // At this point Telecom is requesting the user to select a phone
+ // account. However, Telephony is reporting that the user has a default
+ // outgoing account (which is denoted by a non-negative subId number).
+ // At some point, Telecom and Telephony are out of sync with the default
+ // outgoing calling account.
+ if(mFeatureFlags.telephonyHasDefaultButTelecomDoesNot()) {
+ if (SubscriptionManager.getDefaultVoiceSubscriptionId() !=
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ mAnomalyReporter.reportAnomaly(
+ TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_UUID,
+ TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG);
+ }
+ }
+
// This is the state where the user is expected to select an account
callToPlace.setState(CallState.SELECT_PHONE_ACCOUNT,
"needs account selection");
@@ -2303,6 +2410,15 @@
PhoneAccountHandle phoneAccountHandle = clientExtras.getParcelable(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ PhoneAccount account =
+ mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+ boolean isSelfManaged = account != null && account.isSelfManaged();
+ // Enforce outgoing call restriction for conference calls. This is handled via
+ // UserCallIntentProcessor for normal MO calls.
+ if (UserUtil.hasOutgoingCallsUserRestriction(mContext, initiatingUser,
+ null, isSelfManaged, CallsManager.class.getCanonicalName())) {
+ return;
+ }
CompletableFuture<Call> callFuture = startOutgoingCall(participants, phoneAccountHandle,
clientExtras, initiatingUser, null/* originalIntent */, callingPackage,
true/* isconference*/);
@@ -2879,9 +2995,11 @@
// from the client via a transaction before answering.
call.answer(videoState);
} else {
+ if (!mFeatureFlags.genAnomReportOnFocusTimeout()) {
+ Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+ Log.d(this, "answerCall: Incoming call = %s Ongoing call %s", call, activeCall);
+ }
// Hold or disconnect the active call and request call focus for the incoming call.
- Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
- Log.d(this, "answerCall: Incoming call = %s Ongoing call %s", call, activeCall);
holdActiveCallForNewCall(call);
mConnectionSvrFocusMgr.requestFocus(
call,
@@ -3033,9 +3151,8 @@
* @return {@code true} if the speakerphone should automatically be enabled.
*/
private static boolean isSpeakerEnabledForVideoCalls() {
- return TelephonyProperties.videocall_audio_output()
- .orElse(TelecomManager.AUDIO_OUTPUT_DEFAULT)
- == TelecomManager.AUDIO_OUTPUT_ENABLE_SPEAKER;
+ return SystemProperties.getInt(TelecomManager.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
+ TelecomManager.AUDIO_OUTPUT_DEFAULT) == TelecomManager.AUDIO_OUTPUT_ENABLE_SPEAKER;
}
/**
@@ -3402,7 +3519,7 @@
}
/** Called by the in-call UI to change the mute state. */
- void mute(boolean shouldMute) {
+ public void mute(boolean shouldMute) {
if (isInEmergencyCall() && shouldMute) {
Log.i(this, "Refusing to turn on mute because we're in an emergency call");
shouldMute = false;
@@ -3512,14 +3629,15 @@
* Called when disconnect tone is started or stopped, including any InCallTone
* after disconnected call.
*
+ * @param call
* @param isTonePlaying true if the disconnected tone is started, otherwise the disconnected
- * tone is stopped.
+ * tone is stopped.
*/
@VisibleForTesting
- public void onDisconnectedTonePlaying(boolean isTonePlaying) {
+ public void onDisconnectedTonePlaying(Call call, boolean isTonePlaying) {
Log.v(this, "onDisconnectedTonePlaying, %s", isTonePlaying ? "started" : "stopped");
for (CallsManagerListener listener : mListeners) {
- listener.onDisconnectedTonePlaying(isTonePlaying);
+ listener.onDisconnectedTonePlaying(call, isTonePlaying);
}
}
@@ -3714,11 +3832,6 @@
// Notify listeners that the call was disconnected before being added to CallsManager.
// Listeners will not receive onAdded or onRemoved callbacks.
if (!mCalls.contains(call)) {
- if (call.isEmergencyCall()) {
- mAnomalyReporter.reportAnomaly(
- EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
- EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
- }
mListeners.forEach(l -> l.onCreateConnectionFailed(call));
}
@@ -4153,7 +4266,8 @@
connectTime,
connectElapsedTime,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
// Unlike connections, conferences are not created first and then notified as create
// connection complete from the CS. They originate from the CS and are reported directly to
@@ -4171,7 +4285,10 @@
call.setStatusHints(parcelableConference.getStatusHints());
call.putConnectionServiceExtras(parcelableConference.getExtras());
// For conference calls, set the associated user from the target phone account user handle.
- call.setAssociatedUser(phoneAccount.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.associatedUserRefactorForWorkProfile(), getPhoneAccountRegistrar(),
+ getCurrentUserHandle(), phoneAccount);
+ call.setAssociatedUser(associatedUser);
// In case this Conference was added via a ConnectionManager, keep track of the original
// Connection ID as created by the originating ConnectionService.
Bundle extras = parcelableConference.getExtras();
@@ -4457,10 +4574,6 @@
if (handoverState == HandoverState.HANDOVER_FROM_STARTED) {
// Disconnect before handover was accepted.
Log.i(this, "setCallState: disconnect before handover accepted");
- // Let the handover destination know that the source has disconnected prior to
- // completion of the handover.
- call.getHandoverDestinationCall().sendCallEvent(
- android.telecom.Call.EVENT_HANDOVER_SOURCE_DISCONNECTED, null);
} else if (handoverState == HandoverState.HANDOVER_ACCEPTED) {
Log.i(this, "setCallState: handover from complete");
completeHandoverFrom(call);
@@ -4478,11 +4591,9 @@
// Inform the "from" Call (ie the source call) that the handover from it has
// completed; this allows the InCallService to be notified that a handover it
// initiated completed.
- call.onConnectionEvent(Connection.EVENT_HANDOVER_COMPLETE, null);
call.onHandoverComplete();
// Inform the "to" ConnectionService that handover to it has completed.
- handoverTo.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_COMPLETE, null);
handoverTo.onHandoverComplete();
answerCall(handoverTo, handoverTo.getVideoState());
call.markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_COMPLETE);
@@ -4505,7 +4616,6 @@
// Inform the "from" Call (ie the source call) that the handover from it has
// failed; this allows the InCallService to be notified that a handover it
// initiated failed.
- handoverFrom.onConnectionEvent(Connection.EVENT_HANDOVER_FAILED, null);
handoverFrom.onHandoverFailed(android.telecom.Call.Callback.HANDOVER_FAILURE_USER_REJECTED);
// Inform the "to" ConnectionService that handover to it has failed. This
@@ -4514,7 +4624,6 @@
// Only attempt if the call has a bound ConnectionService if handover failed
// early on in the handover process, the CS will be unbound and we won't be
// able to send the call event.
- handoverTo.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
handoverTo.getConnectionService().handoverFailed(handoverTo,
android.telecom.Call.Callback.HANDOVER_FAILURE_USER_REJECTED);
}
@@ -4737,15 +4846,21 @@
/**
* Determines the number of unholdable calls present in a connection service other than the one
- * the passed phone account belonds to.
+ * the passed phone account belongs to. If a ConnectionService has not been associated with an
+ * outgoing call yet (for example, it is in the SELECT_PHONE_ACCOUNT state), then we do not
+ * count that call because it is not tracked as an active call yet.
* @param phoneAccountHandle The handle of the PhoneAccount.
* @return Number of unholdable calls owned by other connection service.
*/
public int getNumUnholdableCallsForOtherConnectionService(
PhoneAccountHandle phoneAccountHandle) {
return (int) mCalls.stream().filter(call ->
- !phoneAccountHandle.getComponentName().equals(
- call.getTargetPhoneAccount().getComponentName())
+ // If this convention needs to be changed, answerCall will need to be modified to
+ // change what an "active call" is so that the call in SELECT_PHONE_ACCOUNT state
+ // will be properly cancelled.
+ call.getTargetPhoneAccount() != null
+ && !phoneAccountHandle.getComponentName().equals(
+ call.getTargetPhoneAccount().getComponentName())
&& call.getParentCall() == null
&& !call.isExternalCall()
&& !canHold(call)).count();
@@ -5203,7 +5318,8 @@
connection.getConnectTimeMillis() /* connectTimeMillis */,
connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
call.initAnalytics();
call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -5218,7 +5334,10 @@
connection.getCallerDisplayNamePresentation());
// For existing connections, use the phone account user handle to determine the user
// association with the call.
- call.setAssociatedUser(connection.getPhoneAccount().getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.associatedUserRefactorForWorkProfile(), getPhoneAccountRegistrar(),
+ getCurrentUserHandle(), connection.getPhoneAccount());
+ call.setAssociatedUser(associatedUser);
call.addListener(this);
call.putConnectionServiceExtras(connection.getExtras());
@@ -5443,7 +5562,7 @@
mCallAudioManager.getCallAudioModeStateMachine().getHandler().post(() -> {
mainHandlerLatch.countDown();
});
- mCallAudioManager.getCallAudioRouteStateMachine().getHandler().post(() -> {
+ mCallAudioManager.getCallAudioRouteAdapter().getAdapterHandler().post(() -> {
mainHandlerLatch.countDown();
});
@@ -5469,9 +5588,10 @@
// 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);
-
- mPendingCallConfirm.complete(mPendingCall);
- mPendingCallConfirm = null;
+ if (mPendingCallConfirm != null) {
+ mPendingCallConfirm.complete(mPendingCall);
+ mPendingCallConfirm = null;
+ }
mPendingCall = null;
}
}
@@ -5490,8 +5610,10 @@
markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
markCallAsRemoved(mPendingCall);
mPendingCall = null;
- mPendingCallConfirm.complete(null);
- mPendingCallConfirm = null;
+ if (mPendingCallConfirm != null) {
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
+ }
}
}
@@ -5731,7 +5853,8 @@
return;
}
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
- phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+ phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle(),
+ mFeatureFlags);
if (service == null) {
Log.i(this, "Found no connection service.");
return;
@@ -5756,7 +5879,8 @@
return;
}
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
- phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+ phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle(),
+ mFeatureFlags);
if (service == null) {
Log.i(this, "Found no connection service.");
return;
@@ -5793,28 +5917,6 @@
}
/**
- * Called in response to a {@link Call} receiving a {@link Call#sendCallEvent(String, Bundle)}
- * of type {@link android.telecom.Call#EVENT_REQUEST_HANDOVER} indicating the
- * {@link android.telecom.InCallService} has requested a handover to another
- * {@link android.telecom.ConnectionService}.
- *
- * We will explicitly disallow a handover when there is an emergency call present.
- *
- * @param handoverFromCall The {@link Call} to be handed over.
- * @param handoverToHandle The {@link PhoneAccountHandle} to hand over the call to.
- * @param videoState The desired video state of {@link Call} after handover.
- * @param initiatingExtras Extras associated with the handover, to be passed to the handover
- * {@link android.telecom.ConnectionService}.
- */
- private void requestHandoverViaEvents(Call handoverFromCall,
- PhoneAccountHandle handoverToHandle,
- int videoState, Bundle initiatingExtras) {
-
- handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
- Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, "legacy request denied");
- }
-
- /**
* Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
* int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
* handover to another {@link android.telecom.ConnectionService}.
@@ -5860,7 +5962,7 @@
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
- false, mClockProxy, mToastFactory);
+ false, mClockProxy, mToastFactory, mFeatureFlags);
call.initAnalytics();
// Set self-managed and voipAudioMode if destination is self-managed CS
@@ -6067,7 +6169,8 @@
false /* forceAttachToExistingConnection */,
false, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
if (fromCall == null || isHandoverInProgress() ||
!isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount()) ||
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 43f3b90..0c54be3 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -16,10 +16,10 @@
package com.android.server.telecom;
-import android.telecom.AudioState;
import android.telecom.CallAudioState;
import android.telecom.CallEndpoint;
import android.telecom.VideoProfile;
+
import java.util.Set;
/**
@@ -112,7 +112,7 @@
}
@Override
- public void onDisconnectedTonePlaying(boolean isTonePlaying) {
+ public void onDisconnectedTonePlaying(Call call, boolean isTonePlaying) {
}
@Override
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 3694727..35be0f8 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -26,8 +26,11 @@
import android.telecom.Logging.Session;
import android.text.TextUtils;
import android.util.LocalLog;
+import android.util.LogPrinter;
+import android.util.Printer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.Flags;
import com.android.internal.util.IndentingPrintWriter;
import java.util.ArrayList;
@@ -35,6 +38,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -44,6 +48,11 @@
private static final String TAG = "ConnectionSvrFocusMgr";
private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
private final LocalLog mLocalLog = new LocalLog(20);
+ private final AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+ public static final UUID WATCHDOG_GET_CALL_FOCUS_TIMEOUT_UUID =
+ UUID.fromString("edd7334a-ef87-432b-a1d0-a2f23959c73e");
+ public static final String WATCHDOG_GET_CALL_FOCUS_TIMEOUT_MSG =
+ "Telecom CallAnomalyWatchdog detected a timeout while getting the call focus.";
/** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
public interface ConnectionServiceFocusManagerFactory {
@@ -332,8 +341,23 @@
if (syncCallFocus != null) {
return syncCallFocus.orElse(null);
} else {
- Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
- + " inaccurate result");
+ if (Flags.genAnomReportOnFocusTimeout()) {
+ Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+ + " inaccurate result. returning currentFocusCall=[%s]",
+ mCurrentFocusCall);
+
+ // dump the state of the handler to better understand the timeout
+ mEventHandler.dump(
+ new LogPrinter(android.util.Log.INFO, TAG), "CsFocusMgr_timeout");
+
+ // report the timeout
+ mAnomalyReporter.reportAnomaly(
+ WATCHDOG_GET_CALL_FOCUS_TIMEOUT_UUID,
+ WATCHDOG_GET_CALL_FOCUS_TIMEOUT_MSG);
+ } else {
+ Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+ + " inaccurate result");
+ }
return mCurrentFocusCall;
}
} catch (InterruptedException e) {
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index 3991ed5..d6a78d0 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -23,6 +23,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.HashMap;
@@ -61,7 +62,10 @@
}
@VisibleForTesting
- public ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
+ public ConnectionServiceWrapper getService(
+ ComponentName componentName,
+ UserHandle userHandle,
+ FeatureFlags featureFlags) {
Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle);
ConnectionServiceWrapper service = mServiceCache.get(cacheKey);
if (service == null) {
@@ -72,7 +76,8 @@
mCallsManager,
mContext,
mLock,
- userHandle);
+ userHandle,
+ featureFlags);
service.addListener(mUnbindListener);
mServiceCache.put(cacheKey, service);
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
old mode 100755
new mode 100644
index c550ede..43ceff3
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -23,10 +23,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationRequest;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -65,8 +65,11 @@
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.RemoteServiceCallback;
import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.Flags;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -516,8 +519,8 @@
// Check status hints image for cross user access
if (parcelableConference.getStatusHints() != null) {
Icon icon = parcelableConference.getStatusHints().getIcon();
- parcelableConference.getStatusHints().setIcon(StatusHints.
- validateAccountIconUserBoundary(icon, callingUserHandle));
+ parcelableConference.getStatusHints().setIcon(StatusHints
+ .validateAccountIconUserBoundary(icon, callingUserHandle));
}
if (parcelableConference.getConnectElapsedTimeMillis() != 0
@@ -992,6 +995,12 @@
connectIdToCheck = callId;
}
+ // Check status hints image for cross user access
+ if (connection.getStatusHints() != null) {
+ Icon icon = connection.getStatusHints().getIcon();
+ connection.getStatusHints().setIcon(StatusHints.
+ validateAccountIconUserBoundary(icon, userHandle));
+ }
// Handle the case where an existing connection was added by Telephony via
// a connection manager. The remote connection service API does not include
// the ability to specify a parent connection when adding an existing
@@ -1030,14 +1039,6 @@
connection.getCallDirection(),
connection.getCallerNumberVerificationStatus());
}
-
- // Check status hints image for cross user access
- if (connection.getStatusHints() != null) {
- Icon icon = connection.getStatusHints().getIcon();
- connection.getStatusHints().setIcon(StatusHints.
- validateAccountIconUserBoundary(icon, userHandle));
- }
-
// Check to see if this Connection has already been added.
Call alreadyAddedConnection = mCallsManager
.getAlreadyAddedConnection(connectIdToCheck);
@@ -1350,6 +1351,7 @@
private final CallsManager mCallsManager;
private final AppOpsManager mAppOpsManager;
private final Context mContext;
+ private final FeatureFlags mFlags;
private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
@@ -1371,8 +1373,10 @@
CallsManager callsManager,
Context context,
TelecomSystem.SyncRoot lock,
- UserHandle userHandle) {
- super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle);
+ UserHandle userHandle,
+ FeatureFlags featureFlags) {
+ super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle,
+ featureFlags);
mConnectionServiceRepository = connectionServiceRepository;
phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
// TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
@@ -1382,6 +1386,7 @@
mCallsManager = callsManager;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mContext = context;
+ mFlags = featureFlags;
}
/** See {@link IConnectionService#addConnectionServiceAdapter}. */
@@ -1595,7 +1600,6 @@
.setParticipants(call.getParticipants())
.setIsAdhocConferenceCall(call.isAdhocConferenceCall())
.build();
-
try {
mServiceInterface.createConference(
call.getConnectionManagerPhoneAccount(),
@@ -1604,7 +1608,6 @@
call.shouldAttachToExistingConnection(),
call.isUnknown(),
Log.getExternalSession(TELECOM_ABBREVIATION));
-
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConference -- %s", getComponentName());
mPendingResponses.remove(callId).handleCreateConferenceFailure(
@@ -1697,7 +1700,6 @@
.setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
.build();
-
try {
mServiceInterface.createConnection(
call.getConnectionManagerPhoneAccount(),
@@ -2391,6 +2393,7 @@
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
+ if (!isServiceValid("connectionServiceFocusLost")) return;
try {
mServiceInterface.connectionServiceFocusLost(
Log.getExternalSession(TELECOM_ABBREVIATION));
@@ -2410,6 +2413,7 @@
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
+ if (!isServiceValid("connectionServiceFocusGained")) return;
try {
mServiceInterface.connectionServiceFocusGained(
Log.getExternalSession(TELECOM_ABBREVIATION));
@@ -2488,12 +2492,11 @@
*/
private void handleConnectionServiceDeath() {
if (!mPendingResponses.isEmpty()) {
- CreateConnectionResponse[] responses = mPendingResponses.values().toArray(
- new CreateConnectionResponse[mPendingResponses.values().size()]);
+ Collection<CreateConnectionResponse> responses = mPendingResponses.values();
mPendingResponses.clear();
- for (int i = 0; i < responses.length; i++) {
- responses[i].handleCreateConnectionFailure(
- new DisconnectCause(DisconnectCause.ERROR, "CS_DEATH"));
+ for (CreateConnectionResponse response : responses) {
+ response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR,
+ "CS_DEATH"));
}
}
mCallIdMapper.clear();
@@ -2537,7 +2540,7 @@
isCallerConnectionManager = true;
}
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
- handle.getComponentName(), handle.getUserHandle());
+ handle.getComponentName(), handle.getUserHandle(), mFlags);
if (service != null && service != this) {
simServices.add(service);
} else {
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 19691c1..f5b257d 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -32,6 +32,8 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.Flags;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.ArrayList;
import java.util.Collection;
@@ -125,6 +127,7 @@
private DisconnectCause mLastErrorDisconnectCause;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final Context mContext;
+ private final FeatureFlags mFlags;
private CreateConnectionTimeout mTimeout;
private ConnectionServiceWrapper mService;
private int mConnectionAttempt;
@@ -132,7 +135,8 @@
@VisibleForTesting
public CreateConnectionProcessor(
Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
- PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
+ PhoneAccountRegistrar phoneAccountRegistrar, Context context,
+ FeatureFlags featureFlags) {
Log.v(this, "CreateConnectionProcessor created for Call = %s", call);
mCall = call;
mRepository = repository;
@@ -140,6 +144,7 @@
mPhoneAccountRegistrar = phoneAccountRegistrar;
mContext = context;
mConnectionAttempt = 0;
+ mFlags = featureFlags;
}
boolean isProcessingComplete() {
@@ -239,7 +244,7 @@
Log.i(this, "Trying attempt %s", attempt);
PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
mService = mRepository.getService(phoneAccount.getComponentName(),
- phoneAccount.getUserHandle());
+ phoneAccount.getUserHandle(), mFlags);
if (mService == null) {
Log.i(this, "Found no connection service for attempt %s", attempt);
attemptNextPhoneAccount();
@@ -247,7 +252,25 @@
mConnectionAttempt++;
mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
- mCall.setConnectionService(mService);
+ if (mFlags.updatedRcsCallCountTracking()) {
+ if (Objects.equals(attempt.connectionManagerPhoneAccount,
+ attempt.targetPhoneAccount)) {
+ mCall.setConnectionService(mService);
+ } else {
+ PhoneAccountHandle remotePhoneAccount = attempt.targetPhoneAccount;
+ ConnectionServiceWrapper mRemoteService =
+ mRepository.getService(remotePhoneAccount.getComponentName(),
+ remotePhoneAccount.getUserHandle(), mFlags);
+ if (mRemoteService == null) {
+ mCall.setConnectionService(mService);
+ } else {
+ Log.v(this, "attemptNextPhoneAccount Setting RCS = %s", mRemoteService);
+ mCall.setConnectionService(mService, mRemoteService);
+ }
+ }
+ } else {
+ mCall.setConnectionService(mService);
+ }
setTimeoutIfNeeded(mService, attempt);
if (mCall.isIncoming()) {
if (mCall.isAdhocConferenceCall()) {
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
index af79da3..09e927c 100644
--- a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -156,25 +156,26 @@
List<Integer> dataCollectionTypes = getDataCollectionTypes(reason);
boolean invokeTelephonyPersistApi = false;
CallEventTimestamps ts = mEmergencyCallsMap.get(call);
- EmergencyCallDiagnosticParams dp =
- new EmergencyCallDiagnosticParams();
+ EmergencyCallDiagnosticParams.Builder callDiagnosticBuilder =
+ new EmergencyCallDiagnosticParams.Builder();
for (Integer dataCollectionType : dataCollectionTypes) {
switch (dataCollectionType) {
case COLLECTION_TYPE_TELECOM_STATE:
if (isTelecomDumpCollectionEnabled()) {
- dp.setTelecomDumpSysCollection(true);
+ callDiagnosticBuilder.setTelecomDumpSysCollectionEnabled(true);
invokeTelephonyPersistApi = true;
}
break;
case COLLECTION_TYPE_TELEPHONY_STATE:
if (isTelephonyDumpCollectionEnabled()) {
- dp.setTelephonyDumpSysCollection(true);
+ callDiagnosticBuilder.setTelephonyDumpSysCollectionEnabled(true);
invokeTelephonyPersistApi = true;
}
break;
case COLLECTION_TYPE_LOGCAT_BUFFERS:
if (isLogcatCollectionEnabled()) {
- dp.setLogcatCollection(true, ts.getCallCreatedTime());
+ callDiagnosticBuilder.setLogcatCollectionStartTimeMillis(
+ ts.getCallCreatedTime());
invokeTelephonyPersistApi = true;
}
break;
@@ -191,6 +192,7 @@
default:
}
}
+ EmergencyCallDiagnosticParams dp = callDiagnosticBuilder.build();
if (invokeTelephonyPersistApi) {
mAsyncTaskExecutor.execute(new Runnable() {
@Override
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index d5689ae..1946f71 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -22,7 +22,9 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.AttributionSource;
@@ -47,7 +49,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.permission.PermissionManager;
import android.telecom.CallAudioState;
import android.telecom.CallEndpoint;
import android.telecom.ConnectionService;
@@ -66,6 +67,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.SystemStateHelper.SystemStateListener;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.ui.NotificationChannelManager;
import java.util.ArrayList;
@@ -80,6 +82,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -100,6 +103,8 @@
public static final String SET_IN_CALL_ADAPTER_ERROR_MSG =
"Exception thrown while setting the in-call adapter.";
+ private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+
@VisibleForTesting
public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
mAnomalyReporter = mAnomalyReporterAdapter;
@@ -1237,11 +1242,22 @@
private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>();
private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>();
+ private FeatureFlags mFeatureFlags;
public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
- CarModeTracker carModeTracker, ClockProxy clockProxy) {
+ CarModeTracker carModeTracker, ClockProxy clockProxy, FeatureFlags featureFlags) {
+ this(context, lock, callsManager, systemStateHelper, defaultDialerCache, timeoutsAdapter,
+ emergencyCallHelper, carModeTracker, clockProxy, featureFlags, null);
+ }
+
+ @VisibleForTesting
+ public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
+ SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
+ Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
+ CarModeTracker carModeTracker, ClockProxy clockProxy, FeatureFlags featureFlags,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
mContext = context;
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
@@ -1258,6 +1274,13 @@
IntentFilter userAddedFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
userAddedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mUserAddedReceiver, userAddedFilter);
+ mFeatureFlags = featureFlags;
+ if (telephonyFeatureFlags != null) {
+ mTelephonyFeatureFlags = telephonyFeatureFlags;
+ } else {
+ mTelephonyFeatureFlags =
+ new com.android.internal.telephony.flags.FeatureFlagsImpl();
+ }
}
private void restrictPhoneCallOps() {
@@ -1402,17 +1425,31 @@
@Override
public void onCallRemoved(Call call) {
Log.i(this, "onCallRemoved: %s", call);
- if (mCallsManager.getCalls().isEmpty()) {
+ // Instead of checking if there are no active calls, we should check if there any calls with
+ // the same associated user returned from getUserFromCall. For instance, it's possible to
+ // have calls coexist on the personal profile and work profile, in which case, we would only
+ // remove the ICS connection for the user associated with the call to be disconnected.
+ UserHandle userFromCall = getUserFromCall(call);
+ Stream<Call> callsAssociatedWithUserFromCall = mCallsManager.getCalls().stream()
+ .filter((c) -> getUserFromCall(c).equals(userFromCall));
+ boolean isCallCountZero = mFeatureFlags.associatedUserRefactorForWorkProfile()
+ ? callsAssociatedWithUserFromCall.count() == 0
+ : mCallsManager.getCalls().isEmpty();
+ if (isCallCountZero) {
/** Let's add a 2 second delay before we send unbind to the services to hopefully
* give them enough time to process all the pending messages.
*/
mHandler.postDelayed(new Runnable("ICC.oCR", mLock) {
@Override
public void loggedRun() {
- // Check again to make sure there are no active calls.
- if (mCallsManager.getCalls().isEmpty()) {
- unbindFromServices(getUserFromCall(call));
-
+ // Check again to make sure there are no active calls for the associated user.
+ Stream<Call> callsAssociatedWithUserFromCall = mCallsManager.getCalls().stream()
+ .filter((c) -> getUserFromCall(c).equals(userFromCall));
+ boolean isCallCountZero = mFeatureFlags.associatedUserRefactorForWorkProfile()
+ ? callsAssociatedWithUserFromCall.count() == 0
+ : mCallsManager.getCalls().isEmpty();
+ if (isCallCountZero) {
+ unbindFromServices(userFromCall);
mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
}
}
@@ -1690,6 +1727,29 @@
@VisibleForTesting
public void bringToForeground(boolean showDialpad, UserHandle callingUser) {
+ KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
+ boolean isLockscreenRestricted = keyguardManager != null
+ && keyguardManager.isKeyguardLocked();
+ UserHandle currentUser = mCallsManager.getCurrentUserHandle();
+ // Handle cases when calls are placed from the keyguard UI screen, which operates under
+ // the admin user. This needs to account for emergency calls placed from secondary/guest
+ // users as well as the work profile. Once the screen is locked, the user should be able to
+ // return to the call (from the keyguard UI).
+ if (mFeatureFlags.eccKeyguard() && mCallsManager.isInEmergencyCall()
+ && isLockscreenRestricted && !mInCallServices.containsKey(callingUser)) {
+ // If screen is locked and the current user is the system, query calls for the work
+ // profile user, if available. Otherwise, the user is in the secondary/guest profile,
+ // so we can default to the system user.
+ if (currentUser.isSystem()) {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ UserHandle workProfileUser = findChildManagedProfileUser(currentUser, um);
+ boolean hasWorkCalls = mCallsManager.getCalls().stream()
+ .filter((c) -> getUserFromCall(c).equals(workProfileUser)).count() > 0;
+ callingUser = hasWorkCalls ? workProfileUser : currentUser;
+ } else {
+ callingUser = currentUser;
+ }
+ }
if (mInCallServices.containsKey(callingUser)) {
for (IInCallService inCallService : mInCallServices.get(callingUser).values()) {
try {
@@ -1805,6 +1865,7 @@
* Unbinds an existing bound connection to the in-call app.
*/
public void unbindFromServices(UserHandle userHandle) {
+ Log.i(this, "Unbinding from services for user %s", userHandle);
try {
mContext.unregisterReceiver(mPackageChangedReceiver);
} catch (IllegalArgumentException e) {
@@ -1832,13 +1893,14 @@
@VisibleForTesting
public void bindToServices(Call call) {
UserHandle userFromCall = getUserFromCall(call);
- UserHandle parentUser = null;
UserManager um = mContext.getSystemService(UserManager.class);
-
- if (um.isManagedProfile(userFromCall.getIdentifier())) {
+ UserHandle parentUser = mTelephonyFeatureFlags.workProfileApiSplit()
+ ? um.getProfileParent(userFromCall) : null;
+ if (!mTelephonyFeatureFlags.workProfileApiSplit()
+ && um.isManagedProfile(userFromCall.getIdentifier())) {
parentUser = um.getProfileParent(userFromCall);
- Log.i(this, "child:%s parent:%s", userFromCall, parentUser);
}
+ Log.i(this, "child:%s parent:%s", userFromCall, parentUser);
if (!mInCallServiceConnections.containsKey(userFromCall)) {
InCallServiceConnection dialerInCall = null;
@@ -1889,7 +1951,8 @@
// Actually try binding to the UI InCallService.
if (inCallServiceConnection.connect(call) ==
- InCallServiceConnection.CONNECTION_SUCCEEDED || call.isSelfManaged()) {
+ InCallServiceConnection.CONNECTION_SUCCEEDED || (call != null
+ && call.isSelfManaged())) {
// Only connect to the non-ui InCallServices if we actually connected to the main UI
// one, or if the call is self-managed (in which case we'd still want to keep Wear, BT,
// etc. informed.
@@ -1910,19 +1973,20 @@
private void updateNonUiInCallServices(Call call) {
UserHandle userFromCall = getUserFromCall(call);
- UserHandle parentUser = null;
UserManager um = mContext.getSystemService(UserManager.class);
- if(um.isManagedProfile(userFromCall.getIdentifier()))
- {
+ UserHandle parentUser = mTelephonyFeatureFlags.workProfileApiSplit()
+ ? um.getProfileParent(userFromCall) : null;
+
+ if (!mTelephonyFeatureFlags.workProfileApiSplit()
+ && um.isManagedProfile(userFromCall.getIdentifier())) {
parentUser = um.getProfileParent(userFromCall);
}
List<InCallServiceInfo> nonUIInCallComponents =
getInCallServiceComponents(userFromCall, IN_CALL_SERVICE_TYPE_NON_UI);
List<InCallServiceInfo> nonUIInCallComponentsForParent = new ArrayList<>();
- if(parentUser != null)
- {
+ if(parentUser != null) {
//also get Non-UI services using parent handle.
nonUIInCallComponentsForParent =
getInCallServiceComponents(parentUser, IN_CALL_SERVICE_TYPE_NON_UI);
@@ -2121,7 +2185,7 @@
ComponentName foundComponentName =
new ComponentName(serviceInfo.packageName, serviceInfo.name);
- if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
+ if (currentType == IN_CALL_SERVICE_TYPE_NON_UI) {
mKnownNonUiInCallServices.add(foundComponentName);
}
@@ -2293,7 +2357,9 @@
}
// Upon successful connection, send the state of the world to the service.
- List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
+ List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls().stream().filter(
+ call -> getUserFromCall(call).equals(userHandle))
+ .collect(Collectors.toUnmodifiableList()));
Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
"calls", calls.size(), info.getComponentName());
int numCallsSent = 0;
@@ -2430,10 +2496,15 @@
try {
inCallService.updateCall(
sanitizeParcelableCallForService(info, parcelableCall));
- } catch (RemoteException ignored) {
+ } catch (RemoteException exception) {
+ Log.w(this, "Call status update did not send to: "
+ + componentName +" successfully with error " + exception);
}
}
Log.i(this, "Components updated: %s", componentsUpdated);
+ } else {
+ Log.i(this,
+ "Unable to update call. InCallService not found for user: %s", userFromCall);
}
}
@@ -2872,8 +2943,11 @@
} else {
UserHandle userFromCall = call.getAssociatedUser();
UserManager userManager = mContext.getSystemService(UserManager.class);
- // Emergency call should never be blocked, so if the user associated with call is in
- // quite mode, use the primary user for the emergency call.
+ // Emergency call should never be blocked, so if the user associated with the target
+ // phone account handle user is in quiet mode, use the current user for the ecall.
+ // Note, that this only applies to incoming calls that are received on assigned
+ // sims (i.e. work sim), where the associated user would be the target phone account
+ // handle user.
if ((call.isEmergencyCall() || call.isInECBM())
&& (userManager.isQuietModeEnabled(userFromCall)
// We should also account for secondary/guest users where the profile may not
@@ -2885,4 +2959,25 @@
return userFromCall;
}
}
+
+ /**
+ * Useful for debugging purposes and called on the command line via
+ * an "adb shell telecom command".
+ *
+ * @return true if a particular non-ui InCallService package is bound in a call.
+ */
+ public boolean isNonUiInCallServiceBound(String packageName) {
+ for (NonUIInCallServiceConnectionCollection ics : mNonUIInCallServiceConnections.values()) {
+ for (InCallServiceBindingConnection connection : ics.getSubConnections()) {
+ InCallServiceInfo serviceInfo = connection.mInCallServiceInfo;
+ Log.i(this, "isNonUiInCallServiceBound: found serviceInfo=[%s]", serviceInfo);
+ if (serviceInfo != null &&
+ serviceInfo.mComponentName.getPackageName().contains(packageName)) {
+ Log.i(this, "isNonUiInCallServiceBound: found target package");
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 3cc4aac..a5942f0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -69,8 +69,8 @@
mCallAudioManager = callAudioManager;
}
- public InCallTonePlayer createPlayer(int tone) {
- return new InCallTonePlayer(tone, mCallAudioManager,
+ public InCallTonePlayer createPlayer(Call call, int tone) {
+ return new InCallTonePlayer(call, tone, mCallAudioManager,
mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
mMediaPlayerFactory, mAudioManagerAdapter);
}
@@ -212,6 +212,7 @@
private Session mSession;
private final Object mSessionLock = new Object();
+ private final Call mCall;
private final ToneGeneratorFactory mToneGenerator;
private final MediaPlayerFactory mMediaPlayerFactory;
private final AudioManagerAdapter mAudioManagerAdapter;
@@ -228,6 +229,7 @@
* @param toneId ID of the tone to play, see TONE_* constants.
*/
private InCallTonePlayer(
+ Call call,
int toneId,
CallAudioManager callAudioManager,
CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
@@ -235,6 +237,7 @@
ToneGeneratorFactory toneGeneratorFactory,
MediaPlayerFactory mediaPlayerFactor,
AudioManagerAdapter audioManagerAdapter) {
+ mCall = call;
mState = STATE_OFF;
mToneId = toneId;
mCallAudioManager = callAudioManager;
@@ -476,7 +479,7 @@
}
if (sTonesPlaying.incrementAndGet() == 1) {
- mCallAudioManager.setIsTonePlaying(true);
+ mCallAudioManager.setIsTonePlaying(mCall, true);
}
synchronized (mSessionLock) {
@@ -524,7 +527,7 @@
Log.i(InCallTonePlayer.this,
"cleanUpTonePlayer(): tonesPlaying=%d, tone completed", newToneCount);
if (mCallAudioManager != null) {
- mCallAudioManager.setIsTonePlaying(false);
+ mCallAudioManager.setIsTonePlaying(mCall, false);
} else {
Log.w(InCallTonePlayer.this,
"cleanUpTonePlayer(): mCallAudioManager is null!");
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 0e5a287..b0a7c8e 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.annotation.Nullable;
import android.net.Uri;
import android.os.UserHandle;
import android.telecom.PhoneAccountHandle;
@@ -85,7 +86,7 @@
void clearMissedCalls(UserHandle userHandle);
- void showMissedCallNotification(CallInfo call);
+ void showMissedCallNotification(CallInfo call, @Nullable Uri uri);
void reloadAfterBootComplete(CallerInfoLookupHelper callerInfoLookupHelper,
CallInfoFactory callInfoFactory);
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 3b402b1..6070baa 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.Manifest;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
@@ -37,6 +38,7 @@
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
// TODO: Needed for move to system service: import com.android.internal.R;
@@ -77,6 +79,7 @@
private final TelecomSystem.SyncRoot mLock;
private final DefaultDialerCache mDefaultDialerCache;
private final MmiUtils mMmiUtils;
+ private final FeatureFlags mFeatureFlags;
/*
* Whether or not the outgoing call intent originated from the default phone application. If
@@ -100,7 +103,8 @@
@VisibleForTesting
public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
- boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils) {
+ boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils,
+ FeatureFlags featureFlags) {
mContext = context;
mCallsManager = callsManager;
mIntent = intent;
@@ -109,6 +113,7 @@
mLock = mCallsManager.getLock();
mDefaultDialerCache = defaultDialerCache;
mMmiUtils = mmiUtils;
+ mFeatureFlags = featureFlags;
}
/**
@@ -128,7 +133,8 @@
// Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is
// used as the actual number to call. (If null, no call will be placed.)
String resultNumber = getResultData();
- Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
+ Log.i(NewOutgoingCallIntentBroadcaster.this,
+ "Received new-outgoing-call-broadcast for %s with data %s", mCall,
Log.pii(resultNumber));
boolean endEarly = false;
@@ -320,6 +326,7 @@
String scheme = mPhoneNumberUtilsAdapter.isUriNumber(number)
? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
result.callingAddress = Uri.fromParts(scheme, number, null);
+
return result;
}
@@ -351,14 +358,57 @@
public void processCall(Call call, CallDisposition disposition) {
mCall = call;
+
+ // If the new outgoing call broadast doesn't block, trigger the legacy process call
+ // behavior and exit out here.
+ if (!mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) {
+ legacyProcessCall(disposition);
+ return;
+ }
+ boolean callRedirectionWithService = false;
+ // Only try to do redirection if it was requested and we're not calling immediately.
+ // We can expect callImmediately to be true for emergency calls and voip calls.
+ if (disposition.requestRedirection && !disposition.callImmediately) {
+ CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
+ mContext, mCallsManager, mCall, disposition.callingAddress,
+ mCallsManager.getPhoneAccountRegistrar(),
+ getGateWayInfoFromIntent(mIntent, mIntent.getData()),
+ mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
+ false),
+ mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY));
+ /**
+ * If there is an available {@link android.telecom.CallRedirectionService}, use the
+ * {@link CallRedirectionProcessor} to perform call redirection instead of using
+ * broadcasting.
+ */
+ callRedirectionWithService = callRedirectionProcessor
+ .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser());
+ if (callRedirectionWithService) {
+ callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser());
+ }
+ }
+
+ // If no redirection was kicked off, place the call now.
+ if (!callRedirectionWithService) {
+ callImmediately(disposition);
+ }
+
+ // Finally, send the non-blocking broadcast if we're supposed to (ie for any non-voip call).
+ if (disposition.sendBroadcast) {
+ UserHandle targetUser = mCall.getAssociatedUser();
+ broadcastIntent(mIntent, disposition.number, false /* receiverRequired */, targetUser);
+ }
+ }
+
+ /**
+ * The legacy non-flagged version of processing a call. Although there is some code duplication
+ * if makes the new flow cleaner to read.
+ * @param disposition
+ */
+ private void legacyProcessCall(CallDisposition disposition) {
if (disposition.callImmediately) {
- boolean speakerphoneOn = mIntent.getBooleanExtra(
- TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
- int videoState = mIntent.getIntExtra(
- TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY);
- placeOutgoingCallImmediately(mCall, disposition.callingAddress, null,
- speakerphoneOn, videoState);
+ callImmediately(disposition);
// Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
// so that third parties can still inspect (but not intercept) the outgoing call. When
@@ -390,13 +440,26 @@
if (disposition.sendBroadcast) {
UserHandle targetUser = mCall.getAssociatedUser();
- Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
broadcastIntent(mIntent, disposition.number,
!disposition.callImmediately && !callRedirectionWithService, targetUser);
}
}
/**
+ * Place a call immediately.
+ * @param disposition The disposition; used for retrieving the address of the call.
+ */
+ private void callImmediately(CallDisposition disposition) {
+ boolean speakerphoneOn = mIntent.getBooleanExtra(
+ TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
+ int videoState = mIntent.getIntExtra(
+ TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY);
+ placeOutgoingCallImmediately(mCall, disposition.callingAddress, null,
+ speakerphoneOn, videoState);
+ }
+
+ /**
* Sends a new outgoing call ordered broadcast so that third party apps can cancel the
* placement of the call or redirect it to a different number.
*
@@ -415,28 +478,51 @@
if (number != null) {
broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
}
-
- // Force receivers of this broadcast intent to run at foreground priority because we
- // want to finish processing the broadcast intent as soon as possible.
- broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Log.v(this, "Broadcasting intent: %s.", broadcastIntent);
checkAndCopyProviderExtras(originalCallIntent, broadcastIntent);
- final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setBackgroundActivityStartsAllowed(true);
- mContext.sendOrderedBroadcastAsUser(
- broadcastIntent,
- targetUser,
- android.Manifest.permission.PROCESS_OUTGOING_CALLS,
- AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
- options.toBundle(),
- receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
- null, // scheduler
- Activity.RESULT_OK, // initialCode
- number, // initialData: initial value for the result data (number to be modified)
- null); // initialExtras
+ if (mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) {
+ // Where the new outgoing call broadcast is unblocking, do not give receiver FG priority
+ // and do not allow background activity starts.
+ broadcastIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ Log.i(this, "broadcastIntent: Sending non-blocking for %s to %s", mCall.getId(),
+ targetUser);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ mContext.sendBroadcastAsUser(
+ broadcastIntent,
+ targetUser,
+ Manifest.permission.PROCESS_OUTGOING_CALLS);
+ } else {
+ mContext.sendBroadcastAsUser(
+ broadcastIntent,
+ targetUser,
+ android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+ AppOpsManager.OP_PROCESS_OUTGOING_CALLS); // initialExtras
+ }
+ } else {
+ Log.i(this, "broadcastIntent: Sending ordered for %s to %s, waitForResult=%b",
+ mCall.getId(), targetUser, receiverRequired);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setBackgroundActivityStartsAllowed(true);
+ // Force receivers of this broadcast intent to run at foreground priority because we
+ // want to finish processing the broadcast intent as soon as possible.
+ broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+
+ mContext.sendOrderedBroadcastAsUser(
+ broadcastIntent,
+ targetUser,
+ android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+ AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
+ options.toBundle(),
+ receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
+ null, // scheduler
+ Activity.RESULT_OK, // initialCode
+ number, // initialData: initial value for the result data (number to be
+ // modified)
+ null); // initialExtras
+ }
}
/**
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 673b99a..c77e605 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -158,6 +158,10 @@
properties |= android.telecom.Call.Details.PROPERTY_VOIP_AUDIO_MODE;
}
+ if (call.isTransactionalCall()) {
+ properties |= android.telecom.Call.Details.PROPERTY_IS_TRANSACTIONAL;
+ }
+
// If this is a single-SIM device, the "default SIM" will always be the only SIM.
boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index acf07e3..de73269 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -61,6 +61,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.ModifiedUtf8;
+import com.android.server.telecom.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -180,16 +181,19 @@
private interface PhoneAccountRegistrarWriteLock {}
private final PhoneAccountRegistrarWriteLock mWriteLock =
new PhoneAccountRegistrarWriteLock() {};
+ private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@VisibleForTesting
public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock,
- DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
- this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy);
+ DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
+ this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy, telephonyFeatureFlags);
}
@VisibleForTesting
public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock, String fileName,
- DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
+ DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
@@ -203,6 +207,13 @@
mAppLabelProxy = appLabelProxy;
mCurrentUserHandle = Process.myUserHandle();
+ if (telephonyFeatureFlags != null) {
+ mTelephonyFeatureFlags = telephonyFeatureFlags;
+ } else {
+ mTelephonyFeatureFlags =
+ new com.android.internal.telephony.flags.FeatureFlagsImpl();
+ }
+
// register context based receiver to clean up orphan phone accounts
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MANAGED_PROFILE_REMOVED);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -389,26 +400,62 @@
account.getGroupId()));
}
- // Potentially update the default voice subid in SubscriptionManager.
+ // Potentially update the default voice subid in SubscriptionManager so that Telephony and
+ // Telecom are in sync.
int newSubId = accountHandle == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
getSubscriptionIdForPhoneAccount(accountHandle);
- if (isSimAccount || accountHandle == null) {
- int currentVoiceSubId = mSubscriptionManager.getDefaultVoiceSubscriptionId();
- if (newSubId != currentVoiceSubId) {
- Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
- + "account=%s, subId=%d", accountHandle, newSubId);
- mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
+ if (Flags.onlyUpdateTelephonyOnValidSubIds()) {
+ if (shouldUpdateTelephonyDefaultVoiceSubId(accountHandle, isSimAccount, newSubId)) {
+ updateDefaultVoiceSubId(newSubId, accountHandle);
} else {
- Log.i(this, "setUserSelectedOutgoingPhoneAccount: no change to voice sub");
+ Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
}
} else {
- Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
+ if (isSimAccount || accountHandle == null) {
+ updateDefaultVoiceSubId(newSubId, accountHandle);
+ } else {
+ Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
+ }
}
-
write();
fireDefaultOutgoingChanged();
}
+ private void updateDefaultVoiceSubId(int newSubId, PhoneAccountHandle accountHandle){
+ int currentVoiceSubId = mSubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (newSubId != currentVoiceSubId) {
+ Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
+ + "account=%s, subId=%d", accountHandle, newSubId);
+ mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
+ } else {
+ Log.i(this, "setUserSelectedOutgoingPhoneAccount: no change to voice sub");
+ }
+ }
+
+ // This helper is important for CTS testing. [PhoneAccount]s created by Telecom in CTS are
+ // assigned a subId value of INVALID_SUBSCRIPTION_ID (-1) by Telephony. However, when
+ // Telephony has a default outgoing calling voice account of -1, that translates to no default
+ // account (user should be prompted to select an acct when making MOs). In order to avoid
+ // Telephony clearing out the newly changed default [PhoneAccount] in Telecom, Telephony should
+ // not be updated. This situation will never occur in production since [PhoneAccount]s in
+ // production are assigned non-negative subId values.
+ private boolean shouldUpdateTelephonyDefaultVoiceSubId(PhoneAccountHandle phoneAccountHandle,
+ boolean isSimAccount, int newSubId) {
+ // user requests no call preference
+ if (phoneAccountHandle == null) {
+ return true;
+ }
+ // do not update Telephony if the newSubId is invalid
+ if (newSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ Log.w(this, "shouldUpdateTelephonyDefaultVoiceSubId: "
+ + "invalid subId scenario, not updating Telephony. "
+ + "phoneAccountHandle=[%s], isSimAccount=[%b], newSubId=[%s]",
+ phoneAccountHandle, isSimAccount, newSubId);
+ return false;
+ }
+ return isSimAccount;
+ }
+
boolean isUserSelectedSmsPhoneAccount(PhoneAccountHandle accountHandle) {
return getSubscriptionIdForPhoneAccount(accountHandle) ==
SubscriptionManager.getDefaultSmsSubscriptionId();
@@ -660,6 +707,24 @@
}
}
+ private boolean isMatchedUser(PhoneAccount account, UserHandle userHandle) {
+ if (account == null) {
+ return false;
+ }
+
+ if (userHandle == null) {
+ Log.w(this, "userHandle is null in isVisibleForUser");
+ return false;
+ }
+
+ UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
+ if (phoneAccountUserHandle == null) {
+ return false;
+ }
+
+ return phoneAccountUserHandle.equals(userHandle);
+ }
+
private boolean isVisibleForUser(PhoneAccount account, UserHandle userHandle,
boolean acrossProfiles) {
if (account == null) {
@@ -726,11 +791,11 @@
*/
public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle,
boolean crossUserAccess) {
- return getPhoneAccountHandles(0, null, null, false, userHandle, crossUserAccess);
+ return getPhoneAccountHandles(0, null, null, false, userHandle, crossUserAccess, true);
}
public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle, boolean crossUserAccess) {
- return getPhoneAccounts(0, null, null, false, mCurrentUserHandle, crossUserAccess);
+ return getPhoneAccounts(0, null, null, false, mCurrentUserHandle, crossUserAccess, true);
}
/**
@@ -821,7 +886,7 @@
public List<PhoneAccountHandle> getAllPhoneAccountHandlesForPackage(UserHandle userHandle,
String packageName) {
return getPhoneAccountHandles(0, null, packageName, true /* includeDisabled */, userHandle,
- true /* crossUserAccess */);
+ true /* crossUserAccess */, true);
}
/**
@@ -1451,7 +1516,19 @@
UserHandle userHandle,
boolean crossUserAccess) {
return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
- packageName, includeDisabledAccounts, userHandle, crossUserAccess);
+ packageName, includeDisabledAccounts, userHandle, crossUserAccess, false);
+ }
+
+ private List<PhoneAccountHandle> getPhoneAccountHandles(
+ int capabilities,
+ String uriScheme,
+ String packageName,
+ boolean includeDisabledAccounts,
+ UserHandle userHandle,
+ boolean crossUserAccess,
+ boolean includeAll) {
+ return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
+ packageName, includeDisabledAccounts, userHandle, crossUserAccess, includeAll);
}
/**
@@ -1466,11 +1543,24 @@
boolean includeDisabledAccounts,
UserHandle userHandle,
boolean crossUserAccess) {
+ return getPhoneAccountHandles(capabilities, excludedCapabilities, uriScheme, packageName,
+ includeDisabledAccounts, userHandle, crossUserAccess, false);
+ }
+
+ private List<PhoneAccountHandle> getPhoneAccountHandles(
+ int capabilities,
+ int excludedCapabilities,
+ String uriScheme,
+ String packageName,
+ boolean includeDisabledAccounts,
+ UserHandle userHandle,
+ boolean crossUserAccess,
+ boolean includeAll) {
List<PhoneAccountHandle> handles = new ArrayList<>();
for (PhoneAccount account : getPhoneAccounts(
capabilities, excludedCapabilities, uriScheme, packageName,
- includeDisabledAccounts, userHandle, crossUserAccess)) {
+ includeDisabledAccounts, userHandle, crossUserAccess, includeAll)) {
handles.add(account.getAccountHandle());
}
return handles;
@@ -1484,7 +1574,19 @@
UserHandle userHandle,
boolean crossUserAccess) {
return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
- includeDisabledAccounts, userHandle, crossUserAccess);
+ includeDisabledAccounts, userHandle, crossUserAccess, false);
+ }
+
+ private List<PhoneAccount> getPhoneAccounts(
+ int capabilities,
+ String uriScheme,
+ String packageName,
+ boolean includeDisabledAccounts,
+ UserHandle userHandle,
+ boolean crossUserAccess,
+ boolean includeAll) {
+ return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
+ includeDisabledAccounts, userHandle, crossUserAccess, includeAll);
}
/**
@@ -1506,7 +1608,22 @@
boolean includeDisabledAccounts,
UserHandle userHandle,
boolean crossUserAccess) {
+ return getPhoneAccounts(capabilities, excludedCapabilities, uriScheme, packageName,
+ includeDisabledAccounts, userHandle, crossUserAccess, false);
+ }
+
+ @VisibleForTesting
+ public List<PhoneAccount> getPhoneAccounts(
+ int capabilities,
+ int excludedCapabilities,
+ String uriScheme,
+ String packageName,
+ boolean includeDisabledAccounts,
+ UserHandle userHandle,
+ boolean crossUserAccess,
+ boolean includeAll) {
List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
+ List<PhoneAccount> matchedAccounts = new ArrayList<>(mState.accounts.size());
for (PhoneAccount m : mState.accounts) {
if (!(m.isEnabled() || includeDisabledAccounts)) {
// Do not include disabled accounts.
@@ -1540,12 +1657,22 @@
// Not the right package name; skip this one.
continue;
}
+ if (isMatchedUser(m, userHandle)) {
+ matchedAccounts.add(m);
+ }
if (!crossUserAccess && !isVisibleForUser(m, userHandle, false)) {
// Account is not visible for the current user; skip this one.
continue;
}
accounts.add(m);
}
+
+ // Return the account if it exactly matches. Otherwise, return any account that's visible
+ if (mTelephonyFeatureFlags.workProfileApiSplit() && !crossUserAccess && !includeAll
+ && !matchedAccounts.isEmpty()) {
+ return matchedAccounts;
+ }
+
return accounts;
}
@@ -1662,6 +1789,7 @@
} else {
pw.println(defaultOutgoing);
}
+ pw.println("defaultVoiceSubId: " + SubscriptionManager.getDefaultVoiceSubscriptionId());
pw.println("simCallManager: " + getSimCallManager(mCurrentUserHandle));
pw.println("phoneAccounts:");
pw.increaseIndent();
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 1d42db4..2dcd093 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -27,7 +27,6 @@
import android.content.res.Resources;
import android.telecom.Connection;
import android.telecom.Log;
-import android.telecom.Response;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.SubscriptionManager;
@@ -92,7 +91,7 @@
* the main thread.
* @param context The context.
*/
- public void loadCannedTextMessages(final Response<Void, List<String>> response,
+ public void loadCannedTextMessages(final CallsManager.Response<Void, List<String>> response,
final Context context) {
new Thread() {
@Override
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index a8af3ac..5ace9ba 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -19,6 +19,7 @@
import static com.android.server.telecom.LogUtils.Events.START_RINBACK;
import static com.android.server.telecom.LogUtils.Events.STOP_RINGBACK;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import android.telecom.Log;
@@ -42,8 +43,12 @@
*/
private InCallTonePlayer mTonePlayer;
- RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
+ private final Object mLock;
+
+ @VisibleForTesting
+ public RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
mPlayerFactory = playerFactory;
+ mLock = new Object();
}
/**
@@ -52,25 +57,27 @@
* @param call The call for which to ringback.
*/
public void startRingbackForCall(Call call) {
- Preconditions.checkState(call.getState() == CallState.DIALING);
+ synchronized (mLock) {
+ Preconditions.checkState(call.getState() == CallState.DIALING);
- if (mCall == call) {
- Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
- return;
- }
+ if (mCall == call) {
+ Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
+ return;
+ }
- if (mCall != null) {
- // We only get here for the foreground call so, there's no reason why there should
- // exist a current dialing call.
- Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
- }
+ if (mCall != null) {
+ // We only get here for the foreground call so, there's no reason why there should
+ // exist a current dialing call.
+ Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
+ }
- mCall = call;
- if (mTonePlayer == null) {
- Log.i(this, "Playing the ringback tone for %s.", call);
- Log.addEvent(call, START_RINBACK);
- mTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
- mTonePlayer.startTone();
+ mCall = call;
+ if (mTonePlayer == null) {
+ Log.i(this, "Playing the ringback tone for %s.", call);
+ Log.addEvent(call, START_RINBACK);
+ mTonePlayer = mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_RING_BACK);
+ mTonePlayer.startTone();
+ }
}
}
@@ -80,19 +87,27 @@
* @param call The call for which to stop ringback.
*/
public void stopRingbackForCall(Call call) {
- if (mCall == call) {
- // The foreground call is no longer dialing or is no longer the foreground call. In
- // either case, stop the ringback tone.
- mCall = null;
+ synchronized (mLock) {
+ if (mCall == call) {
+ // The foreground call is no longer dialing or is no longer the foreground call. In
+ // either case, stop the ringback tone.
+ mCall = null;
- if (mTonePlayer == null) {
- Log.w(this, "No player found to stop.");
- } else {
- Log.i(this, "Stopping the ringback tone for %s.", call);
- Log.addEvent(call, STOP_RINGBACK);
- mTonePlayer.stopTone();
- mTonePlayer = null;
+ if (mTonePlayer == null) {
+ Log.w(this, "No player found to stop.");
+ } else {
+ Log.i(this, "Stopping the ringback tone for %s.", call);
+ Log.addEvent(call, STOP_RINGBACK);
+ mTonePlayer.stopTone();
+ mTonePlayer = null;
+ }
}
}
}
+
+ public boolean isRingbackPlaying() {
+ synchronized (mLock) {
+ return mTonePlayer != null;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 1710604..3ec4ebe 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -22,10 +22,12 @@
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
import android.content.Context;
+import android.content.res.Resources;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.VolumeShaper;
@@ -38,13 +40,20 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.vibrator.persistence.ParsedVibration;
+import android.os.vibrator.persistence.VibrationXmlParser;
import android.telecom.Log;
import android.telecom.TelecomManager;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.LogUtils.EventTimer;
+import com.android.server.telecom.flags.FeatureFlags;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -59,6 +68,8 @@
*/
@VisibleForTesting
public class Ringer {
+ private static final String TAG = "TelecomRinger";
+
public interface AccessibilityManagerAdapter {
boolean startFlashNotificationSequence(@NonNull Context context,
@AccessibilityManager.FlashNotificationReason int reason);
@@ -84,6 +95,16 @@
// Used for test to notify the completion of RingerAttributes
private CountDownLatch mAttributesLatch;
+ /**
+ * Delay to be used between consecutive vibrations when a non-repeating vibration effect is
+ * provided by the device.
+ *
+ * <p>If looking to customize the loop delay for a device's ring vibration, the desired repeat
+ * behavior should be encoded directly in the effect specification in the device configuration
+ * rather than changing the here (i.e. in `R.raw.default_ringtone_vibration_effect` resource).
+ */
+ private static int DEFAULT_RING_VIBRATION_LOOP_DELAY_MS = 1000;
+
private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming + interval
private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0}; // priming + interval
@@ -96,9 +117,11 @@
private static final int[] PULSE_RAMPING_AMPLITUDE = {
77,77,78,79,81,84,87,93,101,114,133,162,205,255,255,0};
- private static final long[] PULSE_PATTERN;
+ @VisibleForTesting
+ public static final long[] PULSE_PATTERN;
- private static final int[] PULSE_AMPLITUDE;
+ @VisibleForTesting
+ public static final int[] PULSE_AMPLITUDE;
private static final int RAMPING_RINGER_VIBRATION_DURATION = 5000;
private static final int RAMPING_RINGER_DURATION = 10000;
@@ -162,6 +185,7 @@
private final InCallController mInCallController;
private final VibrationEffectProxy mVibrationEffectProxy;
private final boolean mIsHapticPlaybackSupportedByDevice;
+ private final FeatureFlags mFlags;
/**
* For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete
* the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}.
@@ -207,7 +231,8 @@
VibrationEffectProxy vibrationEffectProxy,
InCallController inCallController,
NotificationManager notificationManager,
- AccessibilityManagerAdapter accessibilityManagerAdapter) {
+ AccessibilityManagerAdapter accessibilityManagerAdapter,
+ FeatureFlags featureFlags) {
mLock = new Object();
mSystemSettingsUtil = systemSettingsUtil;
@@ -223,18 +248,15 @@
mNotificationManager = notificationManager;
mAccessibilityManagerAdapter = accessibilityManagerAdapter;
- if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
- mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
- SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
- } else {
- mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN,
- PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
- }
+ mDefaultVibrationEffect =
+ loadDefaultRingVibrationEffect(
+ mContext, mVibrator, mVibrationEffectProxy, featureFlags);
mIsHapticPlaybackSupportedByDevice =
mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
mAudioManager = mContext.getSystemService(AudioManager.class);
+ mFlags = featureFlags;
}
@VisibleForTesting
@@ -456,7 +478,7 @@
};
deferBlockOnRingingFuture = true; // Run in vibrationLogic.
if (ringtoneSupplier != null) {
- mRingtonePlayer.play(ringtoneSupplier, afterRingtoneLogic);
+ mRingtonePlayer.play(ringtoneSupplier, afterRingtoneLogic, isHfpDeviceAttached);
} else {
afterRingtoneLogic.accept(/* ringtone= */ null, /* stopped= */ false);
}
@@ -570,7 +592,7 @@
Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE, reason);
mCallWaitingCall = call;
mCallWaitingPlayer =
- mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_CALL_WAITING);
mCallWaitingPlayer.startTone();
}
}
@@ -629,14 +651,21 @@
Log.i(this, "shouldRingForContact: returning computation from DndCallFilter.");
return !call.isCallSuppressedByDoNotDisturb();
}
- final Uri contactUri = call.getHandle();
- final Bundle peopleExtras = new Bundle();
- if (contactUri != null) {
- ArrayList<Person> personList = new ArrayList<>();
- personList.add(new Person.Builder().setUri(contactUri.toString()).build());
- peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
+ Uri contactUri = call.getHandle();
+ if (mFlags.telecomResolveHiddenDependencies()) {
+ if (contactUri == null) {
+ contactUri = Uri.EMPTY;
+ }
+ return mNotificationManager.matchesCallFilter(contactUri);
+ } else {
+ final Bundle peopleExtras = new Bundle();
+ if (contactUri != null) {
+ ArrayList<Person> personList = new ArrayList<>();
+ personList.add(new Person.Builder().setUri(contactUri.toString()).build());
+ peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
+ }
+ return mNotificationManager.matchesCallFilter(peopleExtras);
}
- return mNotificationManager.matchesCallFilter(peopleExtras);
}
private boolean hasExternalRinger(Call foregroundCall) {
@@ -757,4 +786,65 @@
return false;
}
}
+
+ @Nullable
+ private static VibrationEffect loadSerializedDefaultRingVibration(
+ Resources resources, Vibrator vibrator) {
+ try {
+ InputStream vibrationInputStream =
+ resources.openRawResource(
+ com.android.internal.R.raw.default_ringtone_vibration_effect);
+ ParsedVibration parsedVibration = VibrationXmlParser
+ .parseDocument(
+ new InputStreamReader(vibrationInputStream, StandardCharsets.UTF_8));
+ if (parsedVibration == null) {
+ Log.w(TAG, "Got null parsed default ring vibration effect.");
+ return null;
+ }
+ return parsedVibration.resolve(vibrator);
+ } catch (IOException | Resources.NotFoundException e) {
+ Log.e(TAG, e, "Error parsing default ring vibration effect.");
+ return null;
+ }
+ }
+
+ private static VibrationEffect loadDefaultRingVibrationEffect(
+ Context context,
+ Vibrator vibrator,
+ VibrationEffectProxy vibrationEffectProxy,
+ FeatureFlags featureFlags) {
+ Resources resources = context.getResources();
+
+ if (resources.getBoolean(R.bool.use_simple_vibration_pattern)) {
+ Log.i(TAG, "Using simple default ring vibration.");
+ return createSimpleRingVibration(vibrationEffectProxy);
+ }
+
+ if (featureFlags.useDeviceProvidedSerializedRingerVibration()) {
+ VibrationEffect parsedEffect = loadSerializedDefaultRingVibration(resources, vibrator);
+ if (parsedEffect != null) {
+ Log.i(TAG, "Using parsed default ring vibration.");
+ // Make the parsed effect repeating to make it vibrate continuously during ring.
+ // If the effect is already repeating, this API call is a no-op.
+ // Otherwise, it uses `DEFAULT_RING_VIBRATION_LOOP_DELAY_MS` when changing a
+ // non-repeating vibration to a repeating vibration.
+ // This is so that we ensure consecutive loops of the vibration play with some gap
+ // in between.
+ return parsedEffect.applyRepeatingIndefinitely(
+ /* wantRepeating= */ true, DEFAULT_RING_VIBRATION_LOOP_DELAY_MS);
+ }
+ // Fallback to the simple vibration if the serialized effect cannot be loaded.
+ return createSimpleRingVibration(vibrationEffectProxy);
+ }
+
+ Log.i(TAG, "Using pulse default ring vibration.");
+ return vibrationEffectProxy.createWaveform(
+ PULSE_PATTERN, PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
+ }
+
+ private static VibrationEffect createSimpleRingVibration(
+ VibrationEffectProxy vibrationEffectProxy) {
+ return vibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
+ SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
+ }
}
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 7274993..77f7b2e 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.Collections;
import java.util.Set;
@@ -240,6 +241,8 @@
* Abbreviated form of the package name from {@link #mComponentName}; used for session logging.
*/
protected final String mPackageAbbreviation;
+ private final FeatureFlags mFlags;
+
/** The set of callbacks waiting for notification of the binding's success or failure. */
private final Set<BindCallback> mCallbacks = new ArraySet<>();
@@ -282,7 +285,7 @@
* @param userHandle The {@link UserHandle} to use for binding.
*/
protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
- TelecomSystem.SyncRoot lock, UserHandle userHandle) {
+ TelecomSystem.SyncRoot lock, UserHandle userHandle, FeatureFlags featureFlags) {
Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
Preconditions.checkNotNull(componentName);
@@ -292,6 +295,7 @@
mComponentName = componentName;
mPackageAbbreviation = Log.getPackageAbbreviation(componentName);
mUserHandle = userHandle;
+ mFlags = featureFlags;
}
final UserHandle getUserHandle() {
@@ -305,10 +309,16 @@
}
final void decrementAssociatedCallCount() {
- decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
+ if (mFlags.updatedRcsCallCountTracking()) {
+ decrementAssociatedCallCountUpdated();
+ } else {
+ decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
+ }
}
final void decrementAssociatedCallCount(boolean isSuppressingUnbind) {
+ // This is the legacy method - will be removed after the Flags.updatedRcsCallCountTracking
+ // mendel study completes.
if (mAssociatedCallCount > 0) {
mAssociatedCallCount--;
Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
@@ -323,6 +333,21 @@
}
}
+ final void decrementAssociatedCallCountUpdated() {
+ if (mAssociatedCallCount > 0) {
+ mAssociatedCallCount--;
+ Log.i(this, "Call count decrement %d, %s", mAssociatedCallCount,
+ mComponentName.flattenToShortString());
+
+ if (mAssociatedCallCount == 0) {
+ unbind();
+ }
+ } else {
+ Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
+ mComponentName.getClassName());
+ }
+ }
+
final int getAssociatedCallCount() {
return mAssociatedCallCount;
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f33b185..61fa9ba 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -77,8 +77,10 @@
import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
import com.android.internal.telecom.ITelecomService;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.settings.BlockedNumbersActivity;
import com.android.server.telecom.voip.IncomingCallTransaction;
import com.android.server.telecom.voip.OutgoingCallTransaction;
@@ -88,6 +90,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -354,9 +357,28 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getCallCapablePhoneAccounts(
- boolean includeDisabledAccounts, String callingPackage, String callingFeatureId) {
+ boolean includeDisabledAccounts, String callingPackage,
+ String callingFeatureId, boolean acrossProfiles) {
try {
Log.startSession("TSI.gCCPA", Log.getPackageAbbreviation(callingPackage));
+
+ if (mTelephonyFeatureFlags.workProfileApiSplit()) {
+ if (acrossProfiles) {
+ enforceInAppCrossProfilePermission();
+ }
+
+ if (includeDisabledAccounts && !canReadPrivilegedPhoneState(
+ callingPackage, "getCallCapablePhoneAccounts")) {
+ throw new SecurityException(
+ "Requires READ_PRIVILEGED_PHONE_STATE permission.");
+ }
+
+ if (!includeDisabledAccounts && !canReadPhoneState(callingPackage,
+ callingFeatureId, "Requires READ_PHONE_STATE permission.")) {
+ throw new SecurityException("Requires READ_PHONE_STATE permission.");
+ }
+ }
+
if (includeDisabledAccounts &&
!canReadPrivilegedPhoneState(
callingPackage, "getCallCapablePhoneAccounts")) {
@@ -368,7 +390,11 @@
}
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
- boolean crossUserAccess = hasInAppCrossUserPermission();
+ boolean crossUserAccess = mTelephonyFeatureFlags.workProfileApiSplit()
+ && !acrossProfiles ? false
+ : (mTelephonyFeatureFlags.workProfileApiSplit()
+ ? hasInAppCrossProfilePermission()
+ : hasInAppCrossUserPermission());
long token = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(
@@ -636,6 +662,7 @@
public ParceledListSlice<PhoneAccountHandle> getAllPhoneAccountHandles() {
try {
Log.startSession("TSI.gAPAH");
+
try {
enforceModifyPermission(
"getAllPhoneAccountHandles requires MODIFY_PHONE_STATE permission.");
@@ -654,7 +681,7 @@
.getAllPhoneAccountHandles(callingUserHandle,
crossUserAccess));
} catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
+ Log.e(this, e, "getAllPhoneAccountsHandles");
throw e;
} finally {
Binder.restoreCallingIdentity(token);
@@ -781,6 +808,13 @@
// Validate the profile boundary of the given image URI.
validateAccountIconUserBoundary(account.getIcon());
+ if (mTelephonyFeatureFlags.simultaneousCallingIndications()
+ && account.hasSimultaneousCallingRestriction()) {
+ validateSimultaneousCallingPackageNames(
+ account.getAccountHandle().getComponentName().getPackageName(),
+ account.getSimultaneousCallingRestriction());
+ }
+
final long token = Binder.clearCallingIdentity();
try {
Log.i(this, "registerPhoneAccount: account=%s",
@@ -1551,6 +1585,23 @@
}
mCallIntentProcessorAdapter.processIncomingCallIntent(
mCallsManager, intent);
+ if (mFeatureFlags.earlyBindingToIncallService()) {
+ PhoneAccount account =
+ mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ phoneAccountHandle);
+ Bundle accountExtra =
+ account == null ? new Bundle() : account.getExtras();
+ PackageManager packageManager = mContext.getPackageManager();
+ // Start binding to InCallServices for wearable calls that do not
+ // require call filtering. This is to wake up default dialer earlier
+ // to mitigate InCallService binding latency.
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && accountExtra != null && accountExtra.getBoolean(
+ PhoneAccount.EXTRA_SKIP_CALL_FILTERING,
+ false)) {
+ mCallsManager.getInCallController().bindToServices(null);
+ }
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1967,6 +2018,11 @@
pw.increaseIndent();
Analytics.dump(pw);
pw.decreaseIndent();
+
+ pw.println("Flag Configurations: ");
+ pw.increaseIndent();
+ reflectAndPrintFlagConfigs(pw);
+ pw.decreaseIndent();
}
if (isTimeLineView) {
Log.dumpEventsTimeline(pw);
@@ -1976,6 +2032,28 @@
}
/**
+ * Print all feature flag configurations that Telecom is using for debugging purposes.
+ */
+ private void reflectAndPrintFlagConfigs(IndentingPrintWriter pw) {
+
+ try {
+ // Look away, a forbidden technique (reflection) is being used to allow us to get
+ // all flag configs without having to add them manually to this method.
+ Method[] methods = FeatureFlags.class.getMethods();
+ if (methods.length == 0) {
+ pw.println("NONE");
+ return;
+ }
+ for (Method m : methods) {
+ pw.println(m.getName() + "-> " + m.invoke(mFeatureFlags));
+ }
+ } catch (Exception e) {
+ pw.println("[ERROR]");
+ }
+
+ }
+
+ /**
* @see android.telecom.TelecomManager#createManageBlockedNumbersIntent
*/
@Override
@@ -2138,7 +2216,7 @@
try {
Log.i(this, "handleCallIntent: handling call intent");
mCallIntentProcessorAdapter.processOutgoingCallIntent(mContext,
- mCallsManager, intent, callingPackage);
+ mCallsManager, intent, callingPackage, mFeatureFlags);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2212,6 +2290,39 @@
}
/**
+ * A method intended for use in testing to query whether a particular non-ui inCallService
+ * is bound in a call.
+ * @param packageName of the service to query.
+ * @return whether it is bound or not.
+ */
+ @Override
+ public boolean isNonUiInCallServiceBound(String packageName) {
+ Log.startSession("TCI.iNUICSB");
+ try {
+ synchronized (mLock) {
+ enforceShellOnly(Binder.getCallingUid(), "isNonUiInCallServiceBound");
+ if (!(mContext.checkCallingOrSelfPermission(READ_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED) ||
+ !(mContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("isNonUiInCallServiceBound requires the"
+ + " READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE permission");
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mCallsManager
+ .getInCallController()
+ .isNonUiInCallServiceBound(packageName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
* A method intended for use in testing to reset car mode at all priorities.
*
* Runs during setup to avoid cascading failures from failing car mode CTS.
@@ -2478,6 +2589,9 @@
private final TelecomSystem.SyncRoot mLock;
private TransactionManager mTransactionManager;
private final TransactionalServiceRepository mTransactionalServiceRepository;
+ private final FeatureFlags mFeatureFlags;
+ private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+
public TelecomServiceImpl(
Context context,
@@ -2488,6 +2602,8 @@
DefaultDialerCache defaultDialerCache,
SubscriptionManagerAdapter subscriptionManagerAdapter,
SettingsSecureAdapter settingsSecureAdapter,
+ FeatureFlags featureFlags,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags,
TelecomSystem.SyncRoot lock) {
mContext = context;
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -2495,6 +2611,13 @@
mPackageManager = mContext.getPackageManager();
mCallsManager = callsManager;
+ mFeatureFlags = featureFlags;
+ if (telephonyFeatureFlags != null) {
+ mTelephonyFeatureFlags = telephonyFeatureFlags;
+ } else {
+ mTelephonyFeatureFlags =
+ new com.android.internal.telephony.flags.FeatureFlagsImpl();
+ }
mLock = lock;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
@@ -2707,6 +2830,7 @@
int packageUid = -1;
int callingUid = Binder.getCallingUid();
PackageManager pm;
+ long token = Binder.clearCallingIdentity();
try{
pm = mContext.createContextAsUser(
UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
@@ -2715,6 +2839,8 @@
Log.i(this, "callingUidMatchesPackageManagerRecords:"
+ " createContextAsUser hit exception=[%s]", e.toString());
return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
if (pm != null) {
try {
@@ -2862,12 +2988,24 @@
+ " INTERACT_ACROSS_USERS permission");
}
+ private void enforceInAppCrossProfilePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES, "Must be system or have"
+ + " INTERACT_ACROSS_PROFILES permission");
+ }
+
private boolean hasInAppCrossUserPermission() {
return mContext.checkCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS)
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean hasInAppCrossProfilePermission() {
+ return mContext.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_PROFILES)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
// to be used for TestApi methods that can only be called with SHELL UID.
private void enforceShellOnly(int callingUid, String message) {
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -3162,4 +3300,20 @@
}
}
}
+
+ private void validateSimultaneousCallingPackageNames(String appPackageName,
+ Set<PhoneAccountHandle> handles) {
+ for (PhoneAccountHandle handle : handles) {
+ ComponentName name = handle.getComponentName();
+ if (name == null) {
+ throw new IllegalArgumentException("ComponentName is null");
+ }
+ String restrictionPackageName = name.getPackageName();
+ if (!appPackageName.equals(restrictionPackageName)) {
+ throw new SecurityException("The package name of the PhoneAccount does not "
+ + "match one or more of the package names set in the simultaneous "
+ + "calling restriction.");
+ }
+ }
+ }
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 67bb81f..4310945 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -45,8 +45,12 @@
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraphProvider;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallStreamingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
@@ -223,7 +227,9 @@
DeviceIdleControllerAdapter deviceIdleControllerAdapter,
Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
Executor asyncTaskExecutor,
- BlockedNumbersAdapter blockedNumbersAdapter) {
+ Executor asyncCallAudioTaskExecutor,
+ BlockedNumbersAdapter blockedNumbersAdapter,
+ FeatureFlags featureFlags) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
android.telecom.Log.setLock(mLock);
@@ -239,7 +245,7 @@
try {
mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, mLock, defaultDialerCache,
packageName -> AppLabelProxy.Util.getAppLabel(
- mContext.getPackageManager(), packageName));
+ mContext.getPackageManager(), packageName), null);
mContactsAsyncHelper = contactsAsyncHelperFactory.create(
new ContactsAsyncHelper.ContentResolverAdapter() {
@@ -249,13 +255,19 @@
return context.getContentResolver().openInputStream(uri);
}
});
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker = new
+ CallAudioCommunicationDeviceTracker(mContext);
BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
- mContext.getSystemService(BluetoothManager.class).getAdapter());
+ mContext.getSystemService(BluetoothManager.class).getAdapter(),
+ communicationDeviceTracker, featureFlags);
BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
- bluetoothDeviceManager, new Timeouts.Adapter());
+ bluetoothDeviceManager, new Timeouts.Adapter(),
+ communicationDeviceTracker, featureFlags);
BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
- bluetoothDeviceManager, bluetoothRouteManager);
+ bluetoothDeviceManager, bluetoothRouteManager,
+ communicationDeviceTracker, featureFlags);
mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
+ communicationDeviceTracker.setBluetoothRouteManager(bluetoothRouteManager);
WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock);
@@ -263,7 +275,8 @@
mMissedCallNotifier = missedCallNotifierImplFactory
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar,
defaultDialerCache,
- deviceIdleControllerAdapter);
+ deviceIdleControllerAdapter,
+ featureFlags);
DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory =
new DisconnectedCallNotifier.Default();
@@ -282,7 +295,7 @@
EmergencyCallHelper emergencyCallHelper) {
return new InCallController(context, lock, callsManager, systemStateProvider,
defaultDialerCache, timeoutsAdapter, emergencyCallHelper,
- new CarModeTracker(), clockProxy);
+ new CarModeTracker(), clockProxy, featureFlags);
}
};
@@ -396,10 +409,14 @@
callAnomalyWatchdog,
accessibilityManagerAdapter,
asyncTaskExecutor,
+ asyncCallAudioTaskExecutor,
blockedNumbersAdapter,
transactionManager,
emergencyCallDiagnosticLogger,
- callStreamingNotification);
+ communicationDeviceTracker,
+ callStreamingNotification,
+ featureFlags,
+ IncomingCallFilterGraph::new);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
@@ -443,7 +460,7 @@
}
mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager,
- defaultDialerCache);
+ defaultDialerCache, featureFlags);
mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
mContext, mCallsManager);
@@ -467,6 +484,8 @@
defaultDialerCache,
new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
new TelecomServiceImpl.SettingsSecureAdapterImpl(),
+ featureFlags,
+ null,
mLock);
} finally {
Log.endSession();
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 25aaad7..938ee58 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -46,6 +46,7 @@
import com.android.server.telecom.voip.ParallelTransaction;
import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
import com.android.server.telecom.voip.SerialTransaction;
+import com.android.server.telecom.voip.SetMuteStateTransaction;
import com.android.server.telecom.voip.TransactionManager;
import com.android.server.telecom.voip.VoipCallTransaction;
import com.android.server.telecom.voip.VoipCallTransactionResult;
@@ -225,6 +226,18 @@
}
@Override
+ public void setMuteState(boolean isMuted, android.os.ResultReceiver callback)
+ throws RemoteException {
+ try {
+ Log.startSession("TSW.sMS");
+ addTransactionsToManager(
+ new SetMuteStateTransaction(mCallsManager, isMuted), callback);
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void startCallStreaming(String callId, android.os.ResultReceiver callback)
throws RemoteException {
try {
@@ -451,7 +464,8 @@
@Override
public void onError(CallException exception) {
- Log.i(TAG, "onSetInactive: onError: with e=[%e]", exception);
+ Log.w(TAG, "onSetInactive: onError: e.code=[%d], e.msg=[%s]",
+ exception.getCode(), exception.getMessage());
}
});
} finally {
@@ -498,8 +512,9 @@
@Override
public void onError(CallException exception) {
- Log.i(TAG, "onCallStreamingStarted: onError: with e=[%e]",
- exception);
+ Log.w(TAG, "onCallStreamingStarted: onError: "
+ + "e.code=[%d], e.msg=[%s]",
+ exception.getCode(), exception.getMessage());
stopCallStreaming(call);
}
}
diff --git a/src/com/android/server/telecom/UserUtil.java b/src/com/android/server/telecom/UserUtil.java
index a304401..670ad34 100644
--- a/src/com/android/server/telecom/UserUtil.java
+++ b/src/com/android/server/telecom/UserUtil.java
@@ -16,10 +16,19 @@
package com.android.server.telecom;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.UserInfo;
+import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.flags.FeatureFlags;
public final class UserUtil {
@@ -40,4 +49,90 @@
UserInfo userInfo = getUserInfoFromUserHandle(context, userHandle);
return userInfo != null && userInfo.profileGroupId != userInfo.id;
}
+
+ public static void showErrorDialogForRestrictedOutgoingCall(Context context,
+ int stringId, String tag, String reason) {
+ final Intent intent = new Intent(context, ErrorDialogActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
+ context.startActivityAsUser(intent, UserHandle.CURRENT);
+ Log.w(tag, "Rejecting non-emergency phone call because "
+ + reason);
+ }
+
+ public static boolean hasOutgoingCallsUserRestriction(Context context,
+ UserHandle userHandle, Uri handle, boolean isSelfManaged, String tag) {
+ // Set handle for conference calls. Refer to {@link Connection#ADHOC_CONFERENCE_ADDRESS}.
+ if (handle == null) {
+ handle = Uri.parse("tel:conf-factory");
+ }
+
+ if(!isSelfManaged) {
+ // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+ // check in a managed profile user because this check can always be bypassed
+ // by copying and pasting the phone number into the personal dialer.
+ if (!UserUtil.isManagedProfile(context, userHandle)) {
+ // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+ // restriction.
+ if (!TelephonyUtil.shouldProcessAsEmergency(context, handle)) {
+ final UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ userHandle)) {
+ String reason = "of DISALLOW_OUTGOING_CALLS restriction";
+ showErrorDialogForRestrictedOutgoingCall(context,
+ R.string.outgoing_call_not_allowed_user_restriction, tag, reason);
+ return true;
+ } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ userHandle)) {
+ final DevicePolicyManager dpm =
+ context.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ return true;
+ }
+ final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+ UserManager.DISALLOW_OUTGOING_CALLS);
+ if (adminSupportIntent != null) {
+ context.startActivityAsUser(adminSupportIntent, userHandle);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the associated user for the given call. Note: this is applicable to all calls except
+ * outgoing calls as the associated user is already based off of the user placing the
+ * call.
+ *
+ * @param phoneAccountRegistrar
+ * @param currentUser Current user profile (this can either be the admin or a secondary/guest
+ * user). Note that work profile users fall under the admin user.
+ * @param targetPhoneAccount The phone account to retrieve the {@link UserHandle} from.
+ * @return current user if it isn't the admin or if the work profile is paused for the target
+ * phone account handle user, otherwise return the target phone account handle user. If the
+ * flag is disabled, return the legacy {@link UserHandle}.
+ */
+ public static UserHandle getAssociatedUserForCall(boolean isAssociatedUserFlagEnabled,
+ PhoneAccountRegistrar phoneAccountRegistrar, UserHandle currentUser,
+ PhoneAccountHandle targetPhoneAccount) {
+ if (!isAssociatedUserFlagEnabled) {
+ return targetPhoneAccount.getUserHandle();
+ }
+ // For multi-user phone accounts, associate the call with the profile receiving/placing
+ // the call. For SIM accounts (that are assigned to specific users), the user association
+ // will be placed on the target phone account handle user.
+ PhoneAccount account = phoneAccountRegistrar.getPhoneAccountUnchecked(targetPhoneAccount);
+ if (account != null) {
+ return account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)
+ ? currentUser
+ : targetPhoneAccount.getUserHandle();
+ }
+ // If target phone account handle is null or account cannot be found,
+ // return the current user.
+ return currentUser;
+ }
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 473e7b9..27e5a7d 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -28,10 +28,15 @@
import android.media.AudioManager;
import android.media.AudioDeviceInfo;
import android.media.audio.common.AudioDevice;
+import android.os.Bundle;
import android.telecom.Log;
+import android.util.ArraySet;
import android.util.LocalLog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.ArrayList;
import java.util.Collection;
@@ -58,7 +63,8 @@
public void onGroupStatusChanged(int groupId, int groupStatus) {}
@Override
public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
- Log.i(this, device.getAddress() + " group added " + groupId);
+ Log.i(this, (device == null ? "device is null" : device.getAddress())
+ + " group added " + groupId);
if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
Log.w(this, "invalid parameter");
return;
@@ -70,6 +76,8 @@
}
@Override
public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
+ Log.i(this, (device == null ? "device is null" : device.getAddress())
+ + " group removed " + groupId);
if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
Log.w(this, "invalid parameter");
return;
@@ -85,7 +93,7 @@
new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.startSession("BMSL.oSC");
+ Log.startSession("BPSL.oSC");
try {
synchronized (mLock) {
String logString;
@@ -101,9 +109,13 @@
logString = "Got BluetoothLeAudio: "
+ mBluetoothLeAudioService;
if (!mLeAudioCallbackRegistered) {
- mBluetoothLeAudioService.registerCallback(
- mExecutor, mLeAudioCallbacks);
- mLeAudioCallbackRegistered = true;
+ try {
+ mBluetoothLeAudioService.registerCallback(
+ mExecutor, mLeAudioCallbacks);
+ mLeAudioCallbackRegistered = true;
+ } catch (IllegalStateException e) {
+ logString += ", but Bluetooth is down";
+ }
}
} else {
logString = "Connected to non-requested bluetooth service." +
@@ -119,7 +131,7 @@
@Override
public void onServiceDisconnected(int profile) {
- Log.startSession("BMSL.oSD");
+ Log.startSession("BPSL.oSD");
try {
synchronized (mLock) {
LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
@@ -174,6 +186,12 @@
new LinkedHashMap<>();
private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice =
new LinkedHashMap<>();
+ private final ArrayList<LinkedHashMap<String, BluetoothDevice>>
+ mDevicesByAddressMaps = new ArrayList<LinkedHashMap<String, BluetoothDevice>>(); {
+ mDevicesByAddressMaps.add(mHfpDevicesByAddress);
+ mDevicesByAddressMaps.add(mHearingAidDevicesByAddress);
+ mDevicesByAddressMaps.add(mLeAudioDevicesByAddress);
+ }
private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID;
private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID;
private final LocalLog mLocalLog = new LocalLog(20);
@@ -194,8 +212,12 @@
private BluetoothAdapter mBluetoothAdapter;
private AudioManager mAudioManager;
private Executor mExecutor;
+ private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+ private FeatureFlags mFeatureFlags;
- public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) {
+ public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
if (bluetoothAdapter != null) {
mBluetoothAdapter = bluetoothAdapter;
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -206,6 +228,8 @@
BluetoothProfile.LE_AUDIO);
mAudioManager = context.getSystemService(AudioManager.class);
mExecutor = context.getMainExecutor();
+ mCommunicationDeviceTracker = communicationDeviceTracker;
+ mFeatureFlags = featureFlags;
}
}
@@ -234,18 +258,38 @@
}
public int getNumConnectedDevices() {
- synchronized (mLock) {
- return mHfpDevicesByAddress.size() +
- mHearingAidDevicesByAddress.size() +
- getLeAudioConnectedDevices().size();
- }
+ return getConnectedDevices().size();
}
public Collection<BluetoothDevice> getConnectedDevices() {
synchronized (mLock) {
- ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
+ ArraySet<BluetoothDevice> result = new ArraySet<>();
+
+ // Set storing the group ids of all dual mode audio devices to de-dupe them
+ Set<Integer> dualModeGroupIds = new ArraySet<>();
+ for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) {
+ result.add(hfpDevice);
+ if (mBluetoothLeAudioService == null) {
+ continue;
+ }
+ int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice);
+ if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
+ dualModeGroupIds.add(groupId);
+ }
+ }
+
result.addAll(mHearingAidDevicesByAddress.values());
- result.addAll(getLeAudioConnectedDevices());
+ if (mBluetoothLeAudioService == null) {
+ return Collections.unmodifiableCollection(result);
+ }
+ for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) {
+ // Exclude dual mode audio devices included from the HFP devices list
+ int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice);
+ if (groupId != BluetoothLeAudio.GROUP_ID_INVALID
+ && !dualModeGroupIds.contains(groupId)) {
+ result.add(leAudioDevice);
+ }
+ }
return Collections.unmodifiableCollection(result);
}
}
@@ -253,9 +297,9 @@
// 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;
+ ArraySet<BluetoothDevice> result;
synchronized (mLock) {
- result = new ArrayList<>(mHfpDevicesByAddress.values());
+ result = new ArraySet<>(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
@@ -330,8 +374,10 @@
}
}
- void onDeviceConnected(BluetoothDevice device, int deviceType) {
+ @VisibleForTesting
+ public void onDeviceConnected(BluetoothDevice device, int deviceType) {
synchronized (mLock) {
+ clearDeviceFromDeviceMaps(device.getAddress());
LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
if (deviceType == DEVICE_TYPE_LE_AUDIO) {
if (mBluetoothLeAudioService == null) {
@@ -367,12 +413,20 @@
return;
}
if (!targetDeviceMap.containsKey(device.getAddress())) {
+ Log.i(this, "Adding device with address: " + device + " and devicetype="
+ + getDeviceTypeString(deviceType));
targetDeviceMap.put(device.getAddress(), device);
mBluetoothRouteManager.onDeviceAdded(device.getAddress());
}
}
}
+ void clearDeviceFromDeviceMaps(String deviceAddress) {
+ for (LinkedHashMap<String, BluetoothDevice> deviceMap : mDevicesByAddressMaps) {
+ deviceMap.remove(deviceAddress);
+ }
+ }
+
void onDeviceDisconnected(BluetoothDevice device, int deviceType) {
mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: "
+ deviceType);
@@ -391,6 +445,8 @@
return;
}
if (targetDeviceMap.containsKey(device.getAddress())) {
+ Log.i(this, "Removing device with address: " + device + " and devicetype="
+ + getDeviceTypeString(deviceType));
targetDeviceMap.remove(device.getAddress());
mBluetoothRouteManager.onDeviceLost(device.getAddress());
}
@@ -398,9 +454,14 @@
}
public void disconnectAudio() {
- disconnectSco();
- clearLeAudioCommunicationDevice();
- clearHearingAidCommunicationDevice();
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearBtCommunicationDevice();
+ disconnectSco();
+ } else {
+ disconnectSco();
+ clearLeAudioCommunicationDevice();
+ clearHearingAidCommunicationDevice();
+ }
}
public void disconnectSco() {
@@ -568,50 +629,78 @@
// Connect audio to the bluetooth device at address, checking to see whether it's
// le audio, hearing aid or a HFP device, and using the proper BT API.
public boolean connectAudio(String address, boolean switchingBtDevices) {
+ int callProfile = BluetoothProfile.LE_AUDIO;
+ Log.i(this, "Telecomm connecting audio to device: " + address);
+ BluetoothDevice device = null;
if (mLeAudioDevicesByAddress.containsKey(address)) {
+ Log.i(this, "Telecomm found LE Audio device for address: " + address);
if (mBluetoothLeAudioService == null) {
Log.w(this, "Attempting to turn on audio when the le audio service is null");
return false;
}
- BluetoothDevice device = mLeAudioDevicesByAddress.get(address);
- if (mBluetoothAdapter.setActiveDevice(
- device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
-
- /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
- * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
- * will be audio switched to is available to be choose as communication device */
- if (!switchingBtDevices) {
- return setLeAudioCommunicationDevice();
- }
-
- return true;
- }
- return false;
+ device = mLeAudioDevicesByAddress.get(address);
+ callProfile = BluetoothProfile.LE_AUDIO;
} else if (mHearingAidDevicesByAddress.containsKey(address)) {
+ Log.i(this, "Telecomm found hearing aid device for address: " + address);
if (mBluetoothHearingAid == null) {
Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
return false;
}
- if (mBluetoothAdapter.setActiveDevice(
- mHearingAidDevicesByAddress.get(address),
- BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
-
- /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
- * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
- * will be audio switched to is available to be choose as communication device */
- if (!switchingBtDevices) {
- return setHearingAidCommunicationDevice();
- }
-
- return true;
- }
- return false;
+ device = mHearingAidDevicesByAddress.get(address);
+ callProfile = BluetoothProfile.HEARING_AID;
} else if (mHfpDevicesByAddress.containsKey(address)) {
- BluetoothDevice device = mHfpDevicesByAddress.get(address);
+ Log.i(this, "Telecomm found HFP device for address: " + address);
if (mBluetoothHeadset == null) {
Log.w(this, "Attempting to turn on audio when the headset service is null");
return false;
}
+ device = mHfpDevicesByAddress.get(address);
+ callProfile = BluetoothProfile.HEADSET;
+ }
+
+ if (device == null) {
+ Log.w(this, "No active profiles for Bluetooth address=" + address);
+ return false;
+ }
+
+ Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device);
+ if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
+ && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) {
+ Log.i(this, "Preferred duplex profile for device=" + address + " is "
+ + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
+ callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+ }
+
+ if (callProfile == BluetoothProfile.LE_AUDIO) {
+ if (mBluetoothAdapter.setActiveDevice(
+ device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
+ /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
+ * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
+ * will be audio switched to is available to be choose as communication device */
+ if (!switchingBtDevices) {
+ return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
+ mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+ : setLeAudioCommunicationDevice();
+ }
+ return true;
+ }
+ return false;
+ } else if (callProfile == BluetoothProfile.HEARING_AID) {
+ if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
+ /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
+ * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
+ * will be audio switched to is available to be choose as communication device */
+ if (!switchingBtDevices) {
+ return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
+ mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_HEARING_AID, null)
+ : setHearingAidCommunicationDevice();
+ }
+ return true;
+ }
+ return false;
+ } else if (callProfile == BluetoothProfile.HEADSET) {
boolean success = mBluetoothAdapter.setActiveDevice(device,
BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
if (!success) {
@@ -647,7 +736,9 @@
}
public boolean isInbandRingingEnabled() {
- BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
+ // Get the inband ringing enabled status of expected BT device to route call audio instead
+ // of using the address of currently connected device.
+ BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice();
Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
if (mBluetoothLeAudioService == null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 7966f73..235ba56 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -17,12 +17,14 @@
package com.android.server.telecom.bluetooth;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothLeAudio;
import android.content.Context;
+import android.media.AudioDeviceInfo;
import android.os.Message;
import android.telecom.Log;
import android.telecom.Logging.Session;
@@ -33,8 +35,10 @@
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.flags.FeatureFlags;
import java.util.ArrayList;
import java.util.Collection;
@@ -43,6 +47,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -78,6 +83,7 @@
void onBluetoothActiveDevicePresent();
void onBluetoothActiveDeviceGone();
void onBluetoothAudioConnected();
+ void onBluetoothAudioConnecting();
void onBluetoothAudioDisconnected();
/**
* This gets called when we get an unexpected state change from Bluetooth. Their stack does
@@ -131,7 +137,8 @@
@Override
public void enter() {
BluetoothDevice erroneouslyConnectedDevice = getBluetoothAudioConnectedDevice();
- if (erroneouslyConnectedDevice != null) {
+ if (erroneouslyConnectedDevice != null &&
+ !erroneouslyConnectedDevice.equals(mHearingAidActiveDeviceCache)) {
Log.w(LOG_TAG, "Entering AudioOff state but device %s appears to be connected. " +
"Switching to audio-on state for that device.", erroneouslyConnectedDevice);
// change this to just transition to the new audio on state
@@ -231,8 +238,7 @@
sendMessageDelayed(CONNECTION_TIMEOUT, args,
mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
mContext.getContentResolver()));
- // Pretend like audio is connected when communicating w/ CARSM.
- mListener.onBluetoothAudioConnected();
+ mListener.onBluetoothAudioConnecting();
}
@Override
@@ -250,6 +256,27 @@
SomeArgs args = (SomeArgs) msg.obj;
String address = (String) args.arg2;
boolean switchingBtDevices = !Objects.equals(mDeviceAddress, address);
+
+ if (switchingBtDevices == true) { // check if it is an hearing aid pair
+ BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter();
+ if (bluetoothAdapter != null) {
+ List<BluetoothDevice> activeHearingAids =
+ bluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID);
+ for (BluetoothDevice hearingAid : activeHearingAids) {
+ if (hearingAid != null) {
+ String hearingAidAddress = hearingAid.getAddress();
+ if (hearingAidAddress != null) {
+ if (hearingAidAddress.equals(address) ||
+ hearingAidAddress.equals(mDeviceAddress)) {
+ switchingBtDevices = false;
+ break;
+ }
+ }
+ }
+ }
+
+ }
+ }
try {
switch (msg.what) {
case NEW_DEVICE_CONNECTED:
@@ -391,8 +418,13 @@
String actualAddress = connectBtAudio(address,
true /* switchingBtDevices*/);
if (actualAddress != null) {
- transitionTo(getConnectingStateForAddress(address,
- "AudioConnected/CONNECT_BT"));
+ if (mFeatureFlags.useActualAddressToEnterConnectingState()) {
+ transitionTo(getConnectingStateForAddress(actualAddress,
+ "AudioConnected/CONNECT_BT"));
+ } else {
+ transitionTo(getConnectingStateForAddress(address,
+ "AudioConnected/CONNECT_BT"));
+ }
} else {
Log.w(LOG_TAG, "Tried to connect to %s but failed" +
" to connect to any BT device.", (String) args.arg2);
@@ -469,15 +501,21 @@
private BluetoothDevice mHearingAidActiveDeviceCache = null;
private BluetoothDevice mLeAudioActiveDeviceCache = null;
private BluetoothDevice mMostRecentlyReportedActiveDevice = null;
+ private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+ private FeatureFlags mFeatureFlags;
public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
- BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
+ BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
super(BluetoothRouteManager.class.getSimpleName());
mContext = context;
mLock = lock;
mDeviceManager = deviceManager;
mDeviceManager.setBluetoothRouteManager(this);
mTimeoutsAdapter = timeoutsAdapter;
+ mCommunicationDeviceTracker = communicationDeviceTracker;
+ mFeatureFlags = featureFlags;
mAudioOffState = new AudioOffState();
addState(mAudioOffState);
@@ -621,12 +659,22 @@
if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
mLeAudioActiveDeviceCache = device;
if (device == null) {
- mDeviceManager.clearLeAudioCommunicationDevice();
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
+ } else {
+ mDeviceManager.clearLeAudioCommunicationDevice();
+ }
}
} else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
mHearingAidActiveDeviceCache = device;
if (device == null) {
- mDeviceManager.clearHearingAidCommunicationDevice();
+ if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ AudioDeviceInfo.TYPE_HEARING_AID);
+ } else {
+ mDeviceManager.clearHearingAidCommunicationDevice();
+ }
}
} else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
mHfpActiveDeviceCache = device;
@@ -645,6 +693,10 @@
}
}
+ public BluetoothDevice getMostRecentlyReportedActiveDevice() {
+ return mMostRecentlyReportedActiveDevice;
+ }
+
public boolean hasBtActiveDevice() {
return mLeAudioActiveDeviceCache != null ||
mHearingAidActiveDeviceCache != null ||
@@ -663,6 +715,33 @@
return mDeviceManager.getUniqueConnectedDevices();
}
+ public boolean isWatch(BluetoothDevice device) {
+ if (device == null) {
+ Log.i(this, "isWatch: device is null. Returning false");
+ return false;
+ }
+
+ BluetoothClass deviceClass = device.getBluetoothClass();
+ if (deviceClass != null && deviceClass.getDeviceClass()
+ == BluetoothClass.Device.WEARABLE_WRIST_WATCH) {
+ Log.i(this, "isWatch: bluetooth class component is a WEARABLE_WRIST_WATCH.");
+ return true;
+ }
+
+ // Check metadata
+ byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE);
+ if (deviceType == null) {
+ return false;
+ }
+ String deviceTypeStr = new String(deviceType);
+ if (deviceTypeStr.equals(BluetoothDevice.DEVICE_TYPE_WATCH)) {
+ Log.i(this, "isWatch: bluetooth device type is DEVICE_TYPE_WATCH.");
+ return true;
+ }
+
+ return false;
+ }
+
private String connectBtAudio(String address, boolean switchingBtDevices) {
return connectBtAudio(address, 0, switchingBtDevices);
}
@@ -692,10 +771,19 @@
? address : getActiveDeviceAddress();
if (actualAddress == null) {
Log.i(this, "No device specified and BT stack has no active device."
- + " Using arbitrary device");
+ + " Using arbitrary device - except watch");
if (deviceList.size() > 0) {
- actualAddress = deviceList.iterator().next().getAddress();
- } else {
+ for (BluetoothDevice device : deviceList) {
+ if (mFeatureFlags.ignoreAutoRouteToWatchDevice() && isWatch(device)) {
+ Log.i(this, "Skipping a watch device: " + device);
+ continue;
+ }
+ actualAddress = device.getAddress();
+ break;
+ }
+ }
+
+ if (actualAddress == null) {
Log.i(this, "No devices available at all. Not connecting.");
return null;
}
@@ -797,21 +885,37 @@
}
}
+ boolean isHearingAidSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.isAudioDeviceSetForType(
+ AudioDeviceInfo.TYPE_HEARING_AID)
+ : mDeviceManager.isHearingAidSetAsCommunicationDevice();
if (bluetoothHearingAid != null) {
- if (mDeviceManager.isHearingAidSetAsCommunicationDevice()) {
- for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
- BluetoothProfile.HEARING_AID)) {
- if (device != null) {
- hearingAidActiveDevice = device;
- activeDevices++;
- break;
+ if (isHearingAidSetForCommunication) {
+ List<BluetoothDevice> hearingAidsActiveDevices = bluetoothAdapter.getActiveDevices(
+ BluetoothProfile.HEARING_AID);
+ if (hearingAidsActiveDevices.contains(mHearingAidActiveDeviceCache)) {
+ hearingAidActiveDevice = mHearingAidActiveDeviceCache;
+ activeDevices++;
+ } else {
+ for (BluetoothDevice device : hearingAidsActiveDevices) {
+ if (device != null) {
+ hearingAidActiveDevice = device;
+ activeDevices++;
+ break;
+ }
}
}
}
}
+ boolean isLeAudioSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.isAudioDeviceSetForType(
+ AudioDeviceInfo.TYPE_BLE_HEADSET)
+ : mDeviceManager.isLeAudioCommunicationDevice();
if (bluetoothLeAudio != null) {
- if (mDeviceManager.isLeAudioCommunicationDevice()) {
+ if (isLeAudioSetForCommunication) {
for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
BluetoothProfile.LE_AUDIO)) {
if (device != null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 20af7b5..d2521ac 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.bluetooth;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
@@ -25,10 +26,14 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
import android.telecom.Log;
import android.telecom.Logging.Session;
import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.flags.FeatureFlags;
import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
@@ -54,6 +59,8 @@
private boolean mIsInCall = false;
private final BluetoothRouteManager mBluetoothRouteManager;
private final BluetoothDeviceManager mBluetoothDeviceManager;
+ private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+ private FeatureFlags mFeatureFlags;
public void onReceive(Context context, Intent intent) {
Log.startSession("BSR.oR");
@@ -84,7 +91,7 @@
intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
BluetoothDevice device =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
if (device == null) {
Log.w(LOG_TAG, "Got null device from broadcast. " +
"Ignoring.");
@@ -115,7 +122,7 @@
int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_DISCONNECTED);
BluetoothDevice device =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
if (device == null) {
Log.w(LOG_TAG, "Got null device from broadcast. " +
@@ -149,7 +156,7 @@
private void handleActiveDeviceChanged(Intent intent) {
BluetoothDevice device =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
int deviceType;
if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
@@ -181,19 +188,49 @@
}
args.arg2 = device.getAddress();
+ boolean usePreferredAudioProfile = false;
+ BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter();
+ int preferredDuplexProfile = BluetoothProfile.LE_AUDIO;
+ if (bluetoothAdapter != null) {
+ Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles(
+ device);
+ if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
+ && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)
+ != 0) {
+ Log.i(this, "Preferred duplex profile for device=" + device + " is "
+ + preferredAudioProfiles.getInt(
+ BluetoothAdapter.AUDIO_MODE_DUPLEX));
+ usePreferredAudioProfile = true;
+ preferredDuplexProfile =
+ preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+ }
+ }
+
if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
/* In Le Audio case, once device got Active, the Telecom needs to make sure it
* is set as communication device before we can say that BT_AUDIO_IS_ON
*/
- if (!mBluetoothDeviceManager.setLeAudioCommunicationDevice()) {
+ boolean isLeAudioSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+ : mBluetoothDeviceManager.setLeAudioCommunicationDevice();
+ if ((!usePreferredAudioProfile
+ || preferredDuplexProfile == BluetoothProfile.LE_AUDIO)
+ && !isLeAudioSetForCommunication) {
Log.w(LOG_TAG,
"Device %s cannot be use as LE audio communication device.",
device);
return;
}
} else {
+ boolean isHearingAidSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_HEARING_AID, null)
+ : mBluetoothDeviceManager.setHearingAidCommunicationDevice();
/* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
- if (!mBluetoothDeviceManager.setHearingAidCommunicationDevice()) {
+ if (!isHearingAidSetForCommunication) {
Log.w(LOG_TAG,
"Device %s cannot be use as hearing aid communication device.",
device);
@@ -210,9 +247,13 @@
}
public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
- BluetoothRouteManager routeManager) {
+ BluetoothRouteManager routeManager,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
mBluetoothDeviceManager = deviceManager;
mBluetoothRouteManager = routeManager;
+ mCommunicationDeviceTracker = communicationDeviceTracker;
+ mFeatureFlags = featureFlags;
}
public void setIsInCall(boolean isInCall) {
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
index 36f2077..64060c8 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
@@ -126,6 +126,7 @@
.setShouldReject(true)
.setShouldAddToCallLog(true)
.setShouldShowNotification(false)
+ .setShouldSilence(true)
.setCallBlockReason(getBlockReason(blockStatus))
.setCallScreeningAppName(null)
.setCallScreeningComponentName(null)
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraphProvider.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraphProvider.java
new file mode 100644
index 0000000..1501280
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraphProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.callfiltering;
+
+import android.content.Context;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+/**
+ * Interface to provide a {@link IncomingCallFilterGraph}. This class serve for unit test purpose
+ * to mock an incoming call filter graph in test code.
+ */
+public interface IncomingCallFilterGraphProvider {
+
+
+ /**
+ * Provide a {@link IncomingCallFilterGraph}
+ * @param call The call for the filters.
+ * @param listener Callback object to trigger when filtering is done.
+ * @param context An android context.
+ * @param timeoutsAdapter Adapter to provide timeout value for call filtering.
+ * @param lock Telecom lock.
+ * @return
+ */
+ IncomingCallFilterGraph createGraph(Call call, CallFilterResultCallback listener,
+ Context context,
+ Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock);
+}
diff --git a/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
index 3a0d517..b7e5880 100644
--- a/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
@@ -74,7 +74,7 @@
* @param packageName The name of the removed package.
*/
private void handlePackageRemoved(Context context, String packageName) {
- final TelecomManager telecomManager = TelecomManager.from(context);
+ final TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
if (telecomManager != null) {
telecomManager.clearAccountsForPackage(packageName);
}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index ef85fc7..9287d33 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -45,6 +45,7 @@
import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.DeviceIdleControllerAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockControllerFactory;
@@ -61,6 +62,7 @@
import com.android.server.telecom.TelecomWakeLock;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
import com.android.server.telecom.settings.BlockedNumbersUtil;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
@@ -115,10 +117,11 @@
Context context,
PhoneAccountRegistrar phoneAccountRegistrar,
DefaultDialerCache defaultDialerCache,
- DeviceIdleControllerAdapter idleControllerAdapter) {
+ DeviceIdleControllerAdapter idleControllerAdapter,
+ FeatureFlags featureFlags) {
return new MissedCallNotifierImpl(context,
phoneAccountRegistrar, defaultDialerCache,
- idleControllerAdapter);
+ idleControllerAdapter, featureFlags);
}
},
new CallerInfoAsyncQueryFactory() {
@@ -215,6 +218,7 @@
}
},
Executors.newCachedThreadPool(),
+ Executors.newSingleThreadExecutor(),
new BlockedNumbersAdapter() {
@Override
public boolean shouldShowEmergencyCallNotification(Context
@@ -229,7 +233,8 @@
BlockedNumbersUtil.updateEmergencyCallNotification(context,
showNotification);
}
- }));
+ },
+ new FeatureFlagsImpl()));
}
}
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index a4602c1..41232c2 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -105,47 +105,17 @@
handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
}
- if(!isSelfManaged) {
- // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
- // check in a managed profile user because this check can always be bypassed
- // by copying and pasting the phone number into the personal dialer.
- if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
- // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
- // restriction.
- if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- final UserManager userManager =
- (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- showErrorDialogForRestrictedOutgoingCall(mContext,
- R.string.outgoing_call_not_allowed_user_restriction);
- Log.w(this, "Rejecting non-emergency phone call "
- + "due to DISALLOW_OUTGOING_CALLS restriction");
- return;
- } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- final DevicePolicyManager dpm =
- mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
- return;
- }
- final Intent adminSupportIntent = dpm.createAdminSupportIntent(
- UserManager.DISALLOW_OUTGOING_CALLS);
- if (adminSupportIntent != null) {
- mContext.startActivity(adminSupportIntent);
- }
- return;
- }
- }
- }
- }
+ if (UserUtil.hasOutgoingCallsUserRestriction(mContext, mUserHandle,
+ handle, isSelfManaged, UserCallIntentProcessor.class.getCanonicalName())) {
+ return;
+ }
if (!isSelfManaged && !canCallNonEmergency &&
!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- showErrorDialogForRestrictedOutgoingCall(mContext,
- R.string.outgoing_call_not_allowed_no_permission);
- Log.w(this, "Rejecting non-emergency phone call because "
- + android.Manifest.permission.CALL_PHONE + " permission is not granted.");
+ String reason = android.Manifest.permission.CALL_PHONE + " permission is not granted.";
+ UserUtil.showErrorDialogForRestrictedOutgoingCall(mContext,
+ R.string.outgoing_call_not_allowed_no_permission,
+ this.getClass().getCanonicalName(), reason);
return;
}
@@ -187,11 +157,4 @@
}
return true;
}
-
- private static void showErrorDialogForRestrictedOutgoingCall(Context context, int stringId) {
- final Intent intent = new Intent(context, ErrorDialogActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
- context.startActivityAsUser(intent, UserHandle.CURRENT);
- }
}
diff --git a/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java b/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
index c2a0500..d9feaff 100644
--- a/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
+++ b/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
@@ -72,7 +72,8 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mTelecomManager = TelecomManager.from(getActivity());
+ Context context = getActivity();
+ mTelecomManager = context.getSystemService(TelecomManager.class);
}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 6b97f97..25ce0ca 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -20,6 +20,7 @@
import static android.app.admin.DevicePolicyResources.Strings.Telecomm.NOTIFICATION_MISSED_WORK_CALL_TITLE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
@@ -42,6 +43,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle;
+import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.telecom.CallerInfo;
import android.telecom.Log;
@@ -62,6 +64,7 @@
import com.android.server.telecom.Constants;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.DeviceIdleControllerAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.R;
@@ -87,7 +90,8 @@
MissedCallNotifier makeMissedCallNotifierImpl(Context context,
PhoneAccountRegistrar phoneAccountRegistrar,
DefaultDialerCache defaultDialerCache,
- DeviceIdleControllerAdapter deviceIdleControllerAdapter);
+ DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+ FeatureFlags featureFlags);
}
public interface NotificationBuilderFactory {
@@ -141,19 +145,22 @@
private final Map<UserHandle, Integer> mMissedCallCounts;
private Set<UserHandle> mUsersToLoadAfterBootComplete = new ArraySet<>();
+ private FeatureFlags mFeatureFlags;
public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
DefaultDialerCache defaultDialerCache,
- DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+ DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+ FeatureFlags featureFlags) {
this(context, phoneAccountRegistrar, defaultDialerCache,
- new DefaultNotificationBuilderFactory(), deviceIdleControllerAdapter);
+ new DefaultNotificationBuilderFactory(), deviceIdleControllerAdapter, featureFlags);
}
public MissedCallNotifierImpl(Context context,
PhoneAccountRegistrar phoneAccountRegistrar,
DefaultDialerCache defaultDialerCache,
NotificationBuilderFactory notificationBuilderFactory,
- DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+ DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+ FeatureFlags featureFlags) {
mContext = context;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mNotificationManager =
@@ -163,6 +170,7 @@
mNotificationBuilderFactory = notificationBuilderFactory;
mMissedCallCounts = new ArrayMap<>();
+ mFeatureFlags = featureFlags;
}
/** Clears missed call notification and marks the call log's missed calls as read. */
@@ -261,17 +269,17 @@
}
private void sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo,
- UserHandle userHandle, int missedCallCount) {
+ UserHandle userHandle, int missedCallCount, @Nullable Uri uri) {
Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage)
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
createClearMissedCallsPendingIntent(userHandle))
.putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount)
+ .putExtra(TelecomManager.EXTRA_CALL_LOG_URI, uri)
.putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
callInfo == null ? null : callInfo.getPhoneNumber())
.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
callInfo == null ? null : callInfo.getPhoneAccountHandle());
-
if (missedCallCount == 1 && callInfo != null) {
final Uri handleUri = callInfo.getHandle();
String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
@@ -295,7 +303,7 @@
* @param callInfo The missed call.
*/
@Override
- public void showMissedCallNotification(@NonNull CallInfo callInfo) {
+ public void showMissedCallNotification(@NonNull CallInfo callInfo, @Nullable Uri uri) {
final PhoneAccountHandle phoneAccountHandle = callInfo.getPhoneAccountHandle();
final PhoneAccount phoneAccount =
mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
@@ -306,10 +314,11 @@
} else {
userHandle = phoneAccountHandle.getUserHandle();
}
- showMissedCallNotification(callInfo, userHandle);
+ showMissedCallNotification(callInfo, userHandle, uri);
}
- private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) {
+ private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle,
+ @Nullable Uri uri) {
int missedCallCounts;
synchronized (mMissedCallCountsLock) {
Integer currentCount = mMissedCallCounts.get(userHandle);
@@ -324,7 +333,7 @@
String dialerPackage = getDefaultDialerPackage(userHandle);
if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) {
sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle,
- missedCallCounts);
+ missedCallCounts, uri);
return;
}
@@ -446,7 +455,7 @@
String dialerPackage = getDefaultDialerPackage(userHandle);
if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) {
sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle,
- 0 /* missedCallCount */);
+ /* missedCallCount= */ 0, /* uri= */ null);
return;
}
@@ -631,6 +640,13 @@
while (cursor.moveToNext()) {
// Get data about the missed call from the cursor
final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
+ final Uri uri;
+ if (mFeatureFlags.addCallUriForMissedCalls()){
+ uri = Calls.CONTENT_URI.buildUpon().appendPath(
+ Long.toString(cursor.getInt(CALL_LOG_COLUMN_ID))).build();
+ }else{
+ uri = null;
+ }
final int presentation =
cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION);
final long date = cursor.getLong(CALL_LOG_COLUMN_DATE);
@@ -663,7 +679,8 @@
// null, just show the notification.
CallInfo callInfo = callInfoFactory.makeCallInfo(
info, null, handle, date);
- showMissedCallNotification(callInfo, userHandle);
+ showMissedCallNotification(callInfo, userHandle,
+ /* uri= */ uri);
}
}
@@ -678,7 +695,8 @@
}
CallInfo callInfo = callInfoFactory.makeCallInfo(
info, null, handle, date);
- showMissedCallNotification(callInfo, userHandle);
+ showMissedCallNotification(callInfo, userHandle,
+ /* uri= */ uri);
}
}
);
diff --git a/src/com/android/server/telecom/voip/SetMuteStateTransaction.java b/src/com/android/server/telecom/voip/SetMuteStateTransaction.java
new file mode 100644
index 0000000..d9f7329
--- /dev/null
+++ b/src/com/android/server/telecom/voip/SetMuteStateTransaction.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.voip;
+
+import android.util.Log;
+
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be used to change the global mute state for transactional
+ * calls. There is currently no way for this transaction to fail.
+ */
+public class SetMuteStateTransaction extends VoipCallTransaction {
+
+ private static final String TAG = SetMuteStateTransaction.class.getSimpleName();
+ private final CallsManager mCallsManager;
+ private final boolean mIsMuted;
+
+ public SetMuteStateTransaction(CallsManager callsManager, boolean isMuted) {
+ super(callsManager.getLock());
+ mCallsManager = callsManager;
+ mIsMuted = isMuted;
+ }
+
+ @Override
+ public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction");
+ CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+ mCallsManager.mute(mIsMuted);
+
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_SUCCEED,
+ "The Mute State was changed successfully"));
+
+ return future;
+ }
+}
diff --git a/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java b/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java
new file mode 100644
index 0000000..b17dedd
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.voip;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * VerifyCallStateChangeTransaction is a transaction that verifies a CallState change and has
+ * the ability to disconnect if the CallState is not changed within the timeout window.
+ * <p>
+ * Note: This transaction has a timeout of 2 seconds.
+ */
+public class VerifyCallStateChangeTransaction extends VoipCallTransaction {
+ private static final String TAG = VerifyCallStateChangeTransaction.class.getSimpleName();
+ public static final int FAILURE_CODE = 0;
+ public static final int SUCCESS_CODE = 1;
+ public static final int TIMEOUT_SECONDS = 2;
+ private final Call mCall;
+ private final CallsManager mCallsManager;
+ private final int mTargetCallState;
+ private final boolean mShouldDisconnectUponFailure;
+ private final CompletableFuture<Integer> mCallStateOrTimeoutResult = new CompletableFuture<>();
+ private final CompletableFuture<VoipCallTransactionResult> mTransactionResult =
+ new CompletableFuture<>();
+
+ @VisibleForTesting
+ public Call.CallStateListener mCallStateListenerImpl = new Call.CallStateListener() {
+ @Override
+ public void onCallStateChanged(int newCallState) {
+ Log.d(TAG, "newState=[%d], expectedState=[%d]", newCallState, mTargetCallState);
+ if (newCallState == mTargetCallState) {
+ mCallStateOrTimeoutResult.complete(SUCCESS_CODE);
+ }
+ // NOTE:: keep listening to the call state until the timeout is reached. It's possible
+ // another call state is reached in between...
+ }
+ };
+
+ public VerifyCallStateChangeTransaction(CallsManager callsManager, Call call,
+ int targetCallState, boolean shouldDisconnectUponFailure) {
+ super(callsManager.getLock());
+ mCallsManager = callsManager;
+ mCall = call;
+ mTargetCallState = targetCallState;
+ mShouldDisconnectUponFailure = shouldDisconnectUponFailure;
+ }
+
+ @Override
+ public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction:");
+ // It's possible the Call is already in the expected call state
+ if (isNewCallStateTargetCallState()) {
+ mTransactionResult.complete(
+ new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ TAG));
+ return mTransactionResult;
+ }
+ initCallStateListenerOnTimeout();
+ // At this point, the mCallStateOrTimeoutResult has been completed. There are 2 scenarios:
+ // (1) newCallState == targetCallState --> the transaction is successful
+ // (2) timeout is reached --> evaluate the current call state and complete the t accordingly
+ // also need to do cleanup for the transaction
+ evaluateCallStateUponChangeOrTimeout();
+
+ return mTransactionResult;
+ }
+
+ private boolean isNewCallStateTargetCallState() {
+ return mCall.getState() == mTargetCallState;
+ }
+
+ private void initCallStateListenerOnTimeout() {
+ mCall.addCallStateListener(mCallStateListenerImpl);
+ mCallStateOrTimeoutResult.completeOnTimeout(FAILURE_CODE, TIMEOUT_SECONDS,
+ TimeUnit.SECONDS);
+ }
+
+ private void evaluateCallStateUponChangeOrTimeout() {
+ mCallStateOrTimeoutResult.thenAcceptAsync((result) -> {
+ Log.i(TAG, "processTransaction: thenAcceptAsync: result=[%s]", result);
+ mCall.removeCallStateListener(mCallStateListenerImpl);
+ if (isNewCallStateTargetCallState()) {
+ mTransactionResult.complete(
+ new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ TAG));
+ } else {
+ maybeDisconnectCall();
+ mTransactionResult.complete(
+ new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
+ TAG));
+ }
+ }).exceptionally(exception -> {
+ Log.i(TAG, "hit exception=[%s] while completing future", exception);
+ mTransactionResult.complete(
+ new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
+ TAG));
+ return null;
+ });
+ }
+
+ private void maybeDisconnectCall() {
+ if (mShouldDisconnectUponFailure) {
+ mCallsManager.markCallAsDisconnected(mCall,
+ new DisconnectCause(DisconnectCause.ERROR,
+ "did not hold in timeout window"));
+ mCallsManager.markCallAsRemoved(mCall);
+ }
+ }
+
+ @VisibleForTesting
+ public CompletableFuture<Integer> getCallStateOrTimeoutResult() {
+ return mCallStateOrTimeoutResult;
+ }
+
+ @VisibleForTesting
+ public CompletableFuture<VoipCallTransactionResult> getTransactionResult() {
+ return mTransactionResult;
+ }
+
+ @VisibleForTesting
+ public Call.CallStateListener getCallStateListenerImpl() {
+ return mCallStateListenerImpl;
+ }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
index 3779a6d..8f6ad51 100644
--- a/src/com/android/server/telecom/voip/VoipCallMonitor.java
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -16,6 +16,12 @@
package com.android.server.telecom.voip;
+import static android.app.ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ForegroundServiceDelegationOptions;
@@ -199,8 +205,11 @@
ForegroundServiceDelegationOptions options = new ForegroundServiceDelegationOptions(pid,
uid, handle.getComponentName().getPackageName(), null /* clientAppThread */,
false /* isSticky */, String.valueOf(handle.hashCode()),
- 0 /* foregroundServiceType */,
- ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL);
+ FOREGROUND_SERVICE_TYPE_PHONE_CALL |
+ FOREGROUND_SERVICE_TYPE_MICROPHONE |
+ FOREGROUND_SERVICE_TYPE_CAMERA |
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE /* foregroundServiceTypes */,
+ DELEGATION_SERVICE_PHONE_CALL /* delegationService */);
ServiceConnection fgsConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
diff --git a/src/com/android/server/telecom/voip/VoipCallTransactionResult.java b/src/com/android/server/telecom/voip/VoipCallTransactionResult.java
index 2916fc6..ffc0255 100644
--- a/src/com/android/server/telecom/voip/VoipCallTransactionResult.java
+++ b/src/com/android/server/telecom/voip/VoipCallTransactionResult.java
@@ -24,13 +24,14 @@
public static final int RESULT_SUCCEED = 0;
public static final int RESULT_FAILED = 1;
- private int mResult;
- private String mMessage;
- private Call mCall;
+ private final int mResult;
+ private final String mMessage;
+ private final Call mCall;
public VoipCallTransactionResult(int result, String message) {
mResult = result;
mMessage = message;
+ mCall = null;
}
public VoipCallTransactionResult(int result, Call call, String message) {
@@ -70,7 +71,7 @@
append("{ VoipCallTransactionResult: [mResult: ").
append(mResult).
append("], [mCall: ").
- append(mCall.toString()).
+ append((mCall != null) ? mCall : "null").
append("], [mMessage=").
append(mMessage).append("] }").toString();
}
diff --git a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 1549443..ede06c6 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -82,6 +82,7 @@
* @param videoState The video state requested for the incoming call.
*/
public static void sendIncomingCallIntent(Context context, Uri handle, int videoState) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -94,10 +95,11 @@
extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
}
- TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
+ telecomManager.addNewIncomingCall(phoneAccount, extras);
}
public static void sendIncomingRttCallIntent(Context context, Uri handle, int videoState) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -111,11 +113,12 @@
}
extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
- TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
+ telecomManager.addNewIncomingCall(phoneAccount, extras);
}
public static void addNewUnknownCall(Context context, Uri handle, Bundle extras) {
Log.i(TAG, "Adding new unknown call with handle " + handle);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -129,7 +132,7 @@
extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
}
- TelecomManager.from(context).addNewUnknownCall(phoneAccount, extras);
+ telecomManager.addNewUnknownCall(phoneAccount, extras);
}
public static void hangupCalls(Context context) {
diff --git a/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
index f33022c..d5ddc9b 100644
--- a/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.testapps;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telecom.Log;
@@ -61,7 +62,7 @@
if (connection != null) {
connection.setConnectionDisconnected(DisconnectCause.INCOMING_REJECTED);
connection.destroy();
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
tm.showInCallScreen(false);
}
finish();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 273b060..4a7312c 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -123,6 +123,7 @@
public void registerPhoneAccount(Context context, ComponentName componentName, String id,
Uri address, String name, boolean areCallsLogged) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle handle = new PhoneAccountHandle(componentName, id);
mPhoneAccounts.put(id, handle);
Bundle extras = new Bundle();
@@ -144,7 +145,7 @@
.setExtras(extras)
.setShortDescription(name);
- TelecomManager.from(context).registerPhoneAccount(builder.build());
+ telecomManager.registerPhoneAccount(builder.build());
}
public PhoneAccountHandle getPhoneAccountHandle(String id) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 5cdaf3d..708bae9 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -23,6 +23,7 @@
import android.app.NotificationManager;
import android.app.UiModeManager;
import android.app.role.RoleManager;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
@@ -190,7 +191,7 @@
}
private void placeOutgoingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
@@ -215,7 +216,7 @@
}
private void placeSelfManagedOutgoingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
@@ -233,14 +234,14 @@
}
private void initiateHandover() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
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);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
@@ -263,7 +264,7 @@
}
private void placeSelfManagedIncomingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
SelfManagedCallList.SELF_MANAGED_ACCOUNT_1A);
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 5da7f31..148db51 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -25,8 +25,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -47,11 +47,11 @@
import android.telecom.VideoProfile;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.Analytics;
@@ -381,7 +381,7 @@
waitForHandlerAction(
mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(),
+ .getCallAudioRouteAdapter().getAdapterHandler(),
TEST_TIMEOUT);
waitForHandlerAction(
mTelecomSystem.getCallsManager().getCallAudioManager()
@@ -391,7 +391,7 @@
mInCallServiceFixtureX.getInCallAdapter().setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
waitForHandlerAction(
mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(),
+ .getCallAudioRouteAdapter().getAdapterHandler(),
TEST_TIMEOUT);
waitForHandlerAction(
mTelecomSystem.getCallsManager().getCallAudioManager()
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index bd81a2f..9ca3de1 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -24,11 +24,11 @@
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;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
@@ -39,22 +39,20 @@
import android.content.Context;
import android.content.IContentProvider;
-import android.content.pm.PackageManager;
-import android.media.AudioDeviceInfo;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.BlockedNumberContract;
import android.telecom.Call;
import android.telecom.CallAudioState;
+import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
@@ -65,14 +63,13 @@
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import com.android.internal.telecom.IInCallAdapter;
-import android.telecom.CallerInfo;
import com.google.common.base.Predicate;
@@ -110,6 +107,7 @@
doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
mPackageManager = mContext.getPackageManager();
when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
+ when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
}
@Override
@@ -626,6 +624,48 @@
@LargeTest
@Test
+ public void testIncomingThenOutgoingCalls_AssociatedUsersNotEqual() throws Exception {
+ when(mFeatureFlags.associatedUserRefactorForWorkProfile()).thenReturn(true);
+ InCallServiceFixture.setIgnoreOverrideAdapterFlag(true);
+
+ // Receive incoming call via mPhoneAccountMultiUser
+ IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
+ mPhoneAccountMultiUser.getAccountHandle(), mConnectionServiceFixtureA);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+ // Make outgoing call on mPhoneAccountMultiUser (unassociated sim to simulate guest/
+ // secondary user scenario where both MO/MT calls exist).
+ IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
+ mPhoneAccountMultiUser.getAccountHandle(), mConnectionServiceFixtureA);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+
+ // Outgoing call should be on hold while incoming call is made active
+ mConnectionServiceFixtureA.mConnectionById.get(incoming.mConnectionId).state =
+ Connection.STATE_HOLDING;
+
+ // Swap calls and verify that outgoing call is now the active call while the incoming call
+ // is the held call.
+ mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+ assertEquals(Call.STATE_HOLDING,
+ mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+ assertEquals(Call.STATE_ACTIVE,
+ mInCallServiceFixtureX.getCall(incoming.mCallId).getState());
+
+ // Ensure no issues with call disconnect.
+ mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
+ mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureX.getCall(incoming.mCallId).getState());
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+ InCallServiceFixture.setIgnoreOverrideAdapterFlag(false);
+ }
+
+ @LargeTest
+ @Test
public void testAudioManagerOperations() throws Exception {
AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
@@ -648,15 +688,15 @@
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+ .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor =
ArgumentCaptor.forClass(AudioDeviceInfo.class);
- verify(audioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
- infoArgumentCaptor.capture());
+ verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
+ .setCommunicationDevice(infoArgumentCaptor.capture());
assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+ .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
// setSpeakerPhoneOn(false) gets called once during the call initiation phase
verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
.clearCommunicationDevice();
@@ -667,7 +707,7 @@
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
.getCallAudioModeStateMachine().getHandler(), TEST_TIMEOUT);
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+ .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
verify(audioManager, timeout(TEST_TIMEOUT))
.abandonAudioFocusForCall();
verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
@@ -1195,7 +1235,7 @@
.getState());
mInCallServiceFixtureX.mInCallAdapter.mute(true);
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+ .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
assertTrue(mTelecomSystem.getCallsManager().getAudioState().isMuted());
// Make an emergency call.
@@ -1204,14 +1244,14 @@
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(emergencyCall.mCallId)
.getState());
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+ .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
// Should be unmute automatically.
assertFalse(mTelecomSystem.getCallsManager().getAudioState().isMuted());
// Toggle mute during an emergency call.
mTelecomSystem.getCallsManager().getCallAudioManager().toggleMute();
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+ .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
// Should keep unmute.
assertFalse(mTelecomSystem.getCallsManager().getAudioState().isMuted());
@@ -1339,7 +1379,6 @@
public void testValidateStatusHintsImage_addExistingConnection() throws Exception {
IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
- Connection existingConnection = mConnectionServiceFixtureA.mLatestConnection;
// Modify existing connection with StatusHints image exploit
Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
diff --git a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
index 7df4f29..2584b02 100644
--- a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
@@ -32,11 +32,10 @@
import android.os.Bundle;
import android.os.PersistableBundle;
import android.provider.CallLog;
-import android.telecom.CallerInfo;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallerInfoLookupHelper;
@@ -51,7 +50,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
@@ -72,6 +70,7 @@
private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult.Builder()
.setShouldAllowCall(false)
.setShouldReject(true)
+ .setShouldSilence(true)
.setShouldAddToCallLog(true)
.setShouldShowNotification(false)
.setCallBlockReason(CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER)
diff --git a/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java b/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java
index 56cb735..696867e 100644
--- a/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java
+++ b/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java
@@ -24,7 +24,8 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.settings.BlockedNumbersUtil;
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index c37d136..648a831 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -18,19 +18,36 @@
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
import android.content.Intent;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.os.Bundle;
import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -43,22 +60,8 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
@RunWith(JUnit4.class)
@@ -76,6 +79,7 @@
BluetoothDeviceManager mBluetoothDeviceManager;
BluetoothProfile.ServiceListener serviceListenerUnderTest;
BluetoothStateReceiver receiverUnderTest;
+ CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
ArgumentCaptor<BluetoothLeAudio.Callback> leAudioCallbacksTest;
private BluetoothDevice device1;
@@ -103,8 +107,11 @@
when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L);
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter);
+ mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+ mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter,
+ mCommunicationDeviceTracker, mFeatureFlags);
mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
+ mCommunicationDeviceTracker.setBluetoothRouteManager(mRouteManager);
mockAudioManager = mContext.getSystemService(AudioManager.class);
@@ -114,7 +121,8 @@
serviceCaptor.capture(), eq(BluetoothProfile.HEADSET));
serviceListenerUnderTest = serviceCaptor.getValue();
- receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager, mRouteManager);
+ receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager,
+ mRouteManager, mCommunicationDeviceTracker, mFeatureFlags);
mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset);
mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
@@ -125,6 +133,7 @@
verify(mBluetoothLeAudio).registerCallback(any(), leAudioCallbacksTest.capture());
when(mSpeakerInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(false);
}
@Override
@@ -178,6 +187,7 @@
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+ when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1);
when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
receiverUnderTest.onReceive(mContext,
@@ -188,6 +198,7 @@
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 2);
when(mBluetoothLeAudio.getConnectedGroupLeadDevice(2)).thenReturn(device6);
+ when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1);
receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
@@ -263,17 +274,19 @@
@Test
public void testLeAudioDedup() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device1,
BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device5,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
+ buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device6,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
+ when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1);
+ when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1);
assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
}
@@ -408,8 +421,8 @@
when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
- AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
- when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+ AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(device5.getAddress(),
+ AudioDeviceInfo.TYPE_HEARING_AID);
List<AudioDeviceInfo> devices = new ArrayList<>();
devices.add(mockAudioDeviceInfo);
@@ -429,7 +442,7 @@
when(mockAudioManager.getCommunicationDevice()).thenReturn(mockAudioDeviceInfo);
mBluetoothDeviceManager.disconnectAudio();
- verify(mockAudioManager).clearCommunicationDevice();
+ verify(mockAudioManager, atLeastOnce()).clearCommunicationDevice();
}
@SmallTest
@@ -443,8 +456,8 @@
when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
- AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
- when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(device5.getAddress(),
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
List<AudioDeviceInfo> devices = new ArrayList<>();
devices.add(mockAudioDeviceInfo);
@@ -458,12 +471,14 @@
verify(mBluetoothHeadset, never()).connectAudio();
verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_AUDIO));
receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
mBluetoothDeviceManager.disconnectAudio();
- verify(mockAudioManager).clearCommunicationDevice();
+ verify(mockAudioManager, atLeastOnce()).clearCommunicationDevice();
}
@SmallTest
@@ -485,6 +500,8 @@
verify(mBluetoothHeadset, never()).connectAudio();
verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
.thenReturn(Arrays.asList(device5, device6));
@@ -499,23 +516,219 @@
@SmallTest
@Test
- public void testClearHearingAidCommunicationDevice() {
- AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
- when(mockAudioDeviceInfo.getAddress()).thenReturn(DEVICE_ADDRESS_1);
- when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+ public void testConnectMultipleLeAudioDevices() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ receiverUnderTest.setIsInCall(true);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeAdded(device1, 1);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeAdded(device2, 1);
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
List<AudioDeviceInfo> devices = new ArrayList<>();
- devices.add(mockAudioDeviceInfo);
+ AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
+ AudioDeviceInfo leAudioDevice2 = createMockAudioDeviceInfo(device2.getAddress(),
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
+ devices.add(leAudioDevice1);
+ devices.add(leAudioDevice2);
when(mockAudioManager.getAvailableCommunicationDevices())
.thenReturn(devices);
- when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
+ when(mockAudioManager.setCommunicationDevice(any(AudioDeviceInfo.class)))
.thenReturn(true);
- mBluetoothDeviceManager.setHearingAidCommunicationDevice();
- when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
- mBluetoothDeviceManager.clearHearingAidCommunicationDevice();
+ // Connect LE audio device
+ mBluetoothDeviceManager.connectAudio(device1.getAddress(), false);
+ verify(mAdapter).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset, never()).connectAudio();
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+ // Verify that we set the communication device for device 1
+ verify(mockAudioManager).setCommunicationDevice(leAudioDevice1);
+
+ // Change active device to other LE audio device
+ receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device2,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+
+ // Verify call to clearLeAudioCommunicationDevice
verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
- assertFalse(mBluetoothDeviceManager.isHearingAidSetAsCommunicationDevice());
+ // Verify that we set the communication device for device2
+ verify(mockAudioManager).setCommunicationDevice(leAudioDevice2);
+ }
+
+ @SmallTest
+ @Test
+ public void testClearCommunicationDeviceOnActiveDeviceChange() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ receiverUnderTest.setIsInCall(true);
+
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
+ devices.add(leAudioDevice1);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(any(AudioDeviceInfo.class)))
+ .thenReturn(true);
+
+ // Pretend that the speaker device is currently the requested device set for communication.
+ // This test ensures that the set/clear communication logic for audio switching in/out of BT
+ // is properly working when the receiver processes an active device change intent.
+ mCommunicationDeviceTracker.setTestCommunicationDevice(TYPE_BUILTIN_SPEAKER);
+
+ // Notify that LE audio device has been turned on
+ receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device1,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ // Verify call to clear speaker communication device
+ verify(mockAudioManager).clearCommunicationDevice();
+ // Verify that LE audio communication device was set after clearing the speaker device
+ verify(mockAudioManager).setCommunicationDevice(leAudioDevice1);
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectDualModeEarbud() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ receiverUnderTest.setIsInCall(true);
+
+ // LE Audio earbuds connected
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
+ // HFP device connected
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
+ AudioDeviceInfo mockAudioDevice5Info = createMockAudioDeviceInfo(device5.getAddress(),
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
+ AudioDeviceInfo mockAudioDevice6Info = createMockAudioDeviceInfo(device6.getAddress(),
+ AudioDeviceInfo.TYPE_BLE_HEADSET);
+ when(mockAudioDevice5Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ when(mockAudioDevice6Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ devices.add(mockAudioDevice5Info);
+ devices.add(mockAudioDevice6Info);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(mockAudioDevice5Info))
+ .thenReturn(true);
+
+ Bundle hfpPreferred = new Bundle();
+ hfpPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET);
+ Bundle leAudioPreferred = new Bundle();
+ leAudioPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.LE_AUDIO);
+
+ // TEST 1: LE Audio preferred for DUPLEX
+ when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(leAudioPreferred);
+ when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(leAudioPreferred);
+ mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
+ verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset, never()).connectAudio();
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+ verify(mockAudioManager).setCommunicationDevice(mockAudioDevice5Info);
+
+ when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
+ .thenReturn(Arrays.asList(device5, device6));
+
+ // Check disconnect during a call
+ devices.remove(mockAudioDevice5Info);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeRemoved(device5, 1);
+
+ mBluetoothDeviceManager.connectAudio(device6.getAddress(), false);
+ verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset, never()).connectAudio();
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+ verify(mockAudioManager, times(1)).clearCommunicationDevice();
+
+ // Reconnect other LE Audio earbud
+ devices.add(mockAudioDevice5Info);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+
+ // Disconnects audio
+ mBluetoothDeviceManager.disconnectAudio();
+ verify(mockAudioManager, times(2)).clearCommunicationDevice();
+ verify(mBluetoothHeadset, times(1)).disconnectAudio();
+
+ // TEST 2: HFP preferred for DUPLEX
+ when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(hfpPreferred);
+ when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(hfpPreferred);
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL))).thenReturn(true);
+ mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
+ verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
+ verify(mAdapter, times(1)).setActiveDevice(device5,
+ BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset).connectAudio();
+ mBluetoothDeviceManager.disconnectAudio();
+ verify(mBluetoothHeadset, times(2)).disconnectAudio();
+ }
+
+ @SmallTest
+ @Test
+ public void testClearHearingAidCommunicationDeviceLegacy() {
+ assertClearHearingAidOrLeCommunicationDevice(false, AudioDeviceInfo.TYPE_HEARING_AID);
+ }
+
+ @SmallTest
+ @Test
+ public void testClearHearingAidCommunicationDeviceWithFlag() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ assertClearHearingAidOrLeCommunicationDevice(true, AudioDeviceInfo.TYPE_HEARING_AID);
+ }
+
+ @SmallTest
+ @Test
+ public void testClearLeAudioCommunicationDeviceLegacy() {
+ assertClearHearingAidOrLeCommunicationDevice(false, AudioDeviceInfo.TYPE_BLE_HEADSET);
+ }
+
+ @SmallTest
+ @Test
+ public void testClearLeAudioCommunicationDeviceWithFlag() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ assertClearHearingAidOrLeCommunicationDevice(true, AudioDeviceInfo.TYPE_BLE_HEADSET);
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectedDevicesDoNotContainDuplicateDevices() {
+ BluetoothDevice hfpDevice = mock(BluetoothDevice.class);
+ when(hfpDevice.getAddress()).thenReturn("00:00:00:00:00:00");
+ when(hfpDevice.getType()).thenReturn(BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
+ BluetoothDevice leDevice = mock(BluetoothDevice.class);
+ when(hfpDevice.getAddress()).thenReturn("00:00:00:00:00:00");
+ when(hfpDevice.getType()).thenReturn(BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
+
+ mBluetoothDeviceManager.onDeviceConnected(hfpDevice,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
+ mBluetoothDeviceManager.onDeviceConnected(leDevice,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
+
+ assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
}
@SmallTest
@@ -525,22 +738,67 @@
when(mBluetoothLeAudio.isInbandRingtoneEnabled(1)).thenReturn(true);
when(mBluetoothLeAudio.getGroupId(eq(device3))).thenReturn(1);
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
- BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
- receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
- BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
- receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device3, 1);
- when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
when(mRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(device3);
when(mRouteManager.isCachedLeAudioDevice(eq(device3))).thenReturn(true);
- assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+ when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
+ when(mRouteManager.getMostRecentlyReportedActiveDevice()).thenReturn(device3);
+ assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
assertTrue(mBluetoothDeviceManager.isInbandRingingEnabled());
}
+ private void assertClearHearingAidOrLeCommunicationDevice(
+ boolean flagEnabled, int device_type
+ ) {
+ AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(mockAudioDeviceInfo.getAddress()).thenReturn(DEVICE_ADDRESS_1);
+ when(mockAudioDeviceInfo.getType()).thenReturn(device_type);
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ devices.add(mockAudioDeviceInfo);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
+ .thenReturn(true);
+
+ if (flagEnabled) {
+ BluetoothDevice btDevice = device_type == AudioDeviceInfo.TYPE_BLE_HEADSET
+ ? device1 : null;
+ mCommunicationDeviceTracker.setCommunicationDevice(device_type, btDevice);
+ } else {
+ if (device_type == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+ mBluetoothDeviceManager.setLeAudioCommunicationDevice();
+ } else {
+ mBluetoothDeviceManager.setHearingAidCommunicationDevice();
+ }
+ }
+ when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
+ if (flagEnabled) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(device_type);
+ assertFalse(mCommunicationDeviceTracker.isAudioDeviceSetForType(device_type));
+ } else {
+ if (device_type == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+ mBluetoothDeviceManager.clearLeAudioCommunicationDevice();
+ assertFalse(mBluetoothDeviceManager.isLeAudioCommunicationDevice());
+ } else {
+ mBluetoothDeviceManager.clearHearingAidCommunicationDevice();
+ assertFalse(mBluetoothDeviceManager.isHearingAidSetAsCommunicationDevice());
+ }
+ }
+ verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
+ }
+
+ private AudioDeviceInfo createMockAudioDeviceInfo(String address, int audioType) {
+ AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(mockAudioDeviceInfo.getType()).thenReturn(audioType);
+ if (address != null) {
+ when(mockAudioDeviceInfo.getAddress()).thenReturn(address);
+ }
+ return mockAudioDeviceInfo;
+ }
+
private Intent buildConnectionActionIntent(int state, BluetoothDevice device, int deviceType) {
String intentString;
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 1a6fb88..07dd350 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -16,6 +16,16 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -26,9 +36,11 @@
import android.content.ContentResolver;
import android.os.Parcel;
import android.telecom.Log;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
@@ -46,23 +58,20 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class BluetoothRouteManagerTest extends TelecomTestCase {
private static final int TEST_TIMEOUT = 1000;
static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01");
static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02");
static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03");
- static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04");
+ static final BluetoothDevice HEARING_AID_DEVICE_LEFT = makeBluetoothDevice("CA:FE:DE:CA:00:01");
+ static final BluetoothDevice HEARING_AID_DEVICE_RIGHT =
+ makeBluetoothDevice("CA:FE:DE:CA:00:02");
+ // See HearingAidService#getActiveDevices
+ // Note: It is really important that the left HA is the first one. The left HA is always
+ // in the first index (0) and the right one in the second index (1).
+ static final BluetoothDevice[] HEARING_AIDS =
+ new BluetoothDevice[]{HEARING_AID_DEVICE_LEFT, HEARING_AID_DEVICE_RIGHT};
@Mock private BluetoothAdapter mBluetoothAdapter;
@Mock private BluetoothDeviceManager mDeviceManager;
@@ -71,6 +80,7 @@
@Mock private BluetoothLeAudio mBluetoothLeAudio;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private BluetoothRouteManager.BluetoothStateListener mListener;
+ @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Override
@Before
@@ -86,6 +96,59 @@
@SmallTest
@Test
+ public void testConnectLeftHearingAidWhenLeftIsActive() {
+ BluetoothRouteManager sm = setupStateMachine(
+ BluetoothRouteManager.AUDIO_OFF_STATE_NAME, HEARING_AID_DEVICE_LEFT);
+ sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+ when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
+ when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+
+ setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
+ when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
+ .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+
+ executeRoutingAction(sm,
+ BluetoothRouteManager.NEW_DEVICE_CONNECTED, HEARING_AID_DEVICE_LEFT.getAddress());
+
+ executeRoutingAction(sm,
+ BluetoothRouteManager.CONNECT_BT, HEARING_AID_DEVICE_LEFT.getAddress());
+
+ assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+ + ":" + HEARING_AID_DEVICE_LEFT.getAddress(), sm.getCurrentState().getName());
+
+ sm.quitNow();
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectRightHearingAidWhenLeftIsActive() {
+ BluetoothRouteManager sm = setupStateMachine(
+ BluetoothRouteManager.AUDIO_OFF_STATE_NAME, HEARING_AID_DEVICE_RIGHT);
+ sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
+ BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+ when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
+ when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+
+
+ setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
+ when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
+ .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+
+ executeRoutingAction(sm,
+ BluetoothRouteManager.NEW_DEVICE_CONNECTED, HEARING_AID_DEVICE_LEFT.getAddress());
+
+ executeRoutingAction(sm,
+ BluetoothRouteManager.CONNECT_BT, HEARING_AID_DEVICE_LEFT.getAddress());
+
+ assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+ + ":" + HEARING_AID_DEVICE_LEFT.getAddress(), sm.getCurrentState().getName());
+
+ sm.quitNow();
+ }
+
+ @SmallTest
+ @Test
public void testConnectBtRetryWhileNotConnected() {
BluetoothRouteManager sm = setupStateMachine(
BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
@@ -112,15 +175,15 @@
BluetoothRouteManager sm = setupStateMachine(
BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
setupConnectedDevices(new BluetoothDevice[]{DEVICE1},
- new BluetoothDevice[]{HEARING_AID_DEVICE}, new BluetoothDevice[]{DEVICE2},
- DEVICE1, HEARING_AID_DEVICE, DEVICE2);
+ HEARING_AIDS, new BluetoothDevice[]{DEVICE2},
+ DEVICE1, HEARING_AIDS, DEVICE2);
sm.onActiveDeviceChanged(DEVICE1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
sm.onActiveDeviceChanged(DEVICE2, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
- sm.onActiveDeviceChanged(HEARING_AID_DEVICE,
+ sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
- verifyConnectionAttempt(HEARING_AID_DEVICE, 0);
+ verifyConnectionAttempt(HEARING_AID_DEVICE_LEFT, 0);
verifyConnectionAttempt(DEVICE1, 0);
verifyConnectionAttempt(DEVICE2, 0);
assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
@@ -171,11 +234,52 @@
sm.quitNow();
}
+ @SmallTest
+ @Test
+ public void testSkipInactiveBtDeviceWhenEvaluateActualState() {
+ BluetoothRouteManager sm = setupStateMachine(
+ BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, HEARING_AID_DEVICE_LEFT);
+ setupConnectedDevices(null, HEARING_AIDS,
+ null, null, HEARING_AIDS, null);
+ executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST,
+ HEARING_AID_DEVICE_LEFT.getAddress());
+ assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName());
+ sm.quitNow();
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectBtWithoutAddress() {
+ when(mFeatureFlags.useActualAddressToEnterConnectingState()).thenReturn(true);
+ BluetoothRouteManager sm = setupStateMachine(
+ BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
+ setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null,
+ null);
+ when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
+ nullable(ContentResolver.class))).thenReturn(0L);
+ when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
+ executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, null);
+ // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
+ // so wait twice for the retry attempt, again to make sure there are only three attempts,
+ // and once more for good luck.
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ verifyConnectionAttempt(DEVICE1, 1);
+ assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+ + ":" + DEVICE1.getAddress(),
+ sm.getCurrentState().getName());
+ sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
+ sm.quitNow();
+ }
+
private BluetoothRouteManager setupStateMachine(String initialState,
BluetoothDevice initialDevice) {
resetMocks();
BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
- new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
+ new TelecomSystem.SyncRoot() { }, mDeviceManager,
+ mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
sm.setListener(mListener);
sm.setInitialStateForTesting(initialState, initialDevice);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -185,10 +289,11 @@
private void setupConnectedDevices(BluetoothDevice[] hfpDevices,
BluetoothDevice[] hearingAidDevices, BluetoothDevice[] leAudioDevices,
- BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice,
+ BluetoothDevice hfpActiveDevice, BluetoothDevice[] hearingAidActiveDevices,
BluetoothDevice leAudioDevice) {
if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{};
if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{};
+ if (hearingAidActiveDevices == null) hearingAidActiveDevices = new BluetoothDevice[]{};
if (leAudioDevice == null) leAudioDevices = new BluetoothDevice[]{};
when(mDeviceManager.getNumConnectedDevices()).thenReturn(
@@ -207,7 +312,7 @@
when(mBluetoothHearingAid.getConnectedDevices())
.thenReturn(Arrays.asList(hearingAidDevices));
when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID)))
- .thenReturn(Arrays.asList(hearingAidActiveDevice, null));
+ .thenReturn(Arrays.asList(hearingAidActiveDevices));
when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
.thenReturn(Arrays.asList(leAudioDevice, null));
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index 5eecccc..c546c3f 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -16,6 +16,21 @@
package com.android.server.telecom.tests;
+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.verify;
+import static org.mockito.Mockito.when;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -25,9 +40,11 @@
import android.bluetooth.BluetoothStatusCodes;
import android.content.ContentResolver;
import android.telecom.Log;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
@@ -45,28 +62,13 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.stream.Stream;
import java.util.stream.Collectors;
-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.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(Parameterized.class)
public class BluetoothRouteTransitionTests extends TelecomTestCase {
private enum ListenerUpdate {
DEVICE_LIST_CHANGED, ACTIVE_DEVICE_PRESENT, ACTIVE_DEVICE_GONE,
- AUDIO_CONNECTED, AUDIO_DISCONNECTED, UNEXPECTED_STATE_CHANGE
+ AUDIO_CONNECTING, AUDIO_CONNECTED, AUDIO_DISCONNECTED, UNEXPECTED_STATE_CHANGE
}
private static class BluetoothRouteTestParametersBuilder {
@@ -263,6 +265,7 @@
@Mock private BluetoothLeAudio mBluetoothLeAudio;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private BluetoothRouteManager.BluetoothStateListener mListener;
+ @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Override
@Before
@@ -348,6 +351,9 @@
case ACTIVE_DEVICE_GONE:
verify(mListener).onBluetoothActiveDeviceGone();
break;
+ case AUDIO_CONNECTING:
+ verify(mListener).onBluetoothAudioConnecting();
+ break;
case AUDIO_CONNECTED:
verify(mListener).onBluetoothAudioConnected();
break;
@@ -413,7 +419,8 @@
when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
nullable(ContentResolver.class))).thenReturn(100000L);
BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
- new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
+ new TelecomSystem.SyncRoot() { }, mDeviceManager,
+ mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
sm.setListener(mListener);
sm.setInitialStateForTesting(initialState, initialDevice);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -449,7 +456,7 @@
.setConnectedDevices(DEVICE2, DEVICE1)
.setActiveDevice(DEVICE1)
.setMessageType(BluetoothRouteManager.CONNECT_BT)
- .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTING)
.setExpectedBluetoothInteraction(CONNECT)
.setExpectedConnectionDevice(DEVICE1)
.setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
@@ -505,7 +512,7 @@
.setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
.setMessageType(BluetoothRouteManager.CONNECT_BT)
.setMessageDevice(DEVICE3)
- .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTING)
.setExpectedBluetoothInteraction(CONNECT_SWITCH_DEVICE)
.setExpectedConnectionDevice(DEVICE3)
.setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
@@ -519,7 +526,7 @@
.setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
.setMessageType(BluetoothRouteManager.CONNECT_BT)
.setMessageDevice(DEVICE3)
- .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTING)
.setExpectedBluetoothInteraction(CONNECT_SWITCH_DEVICE)
.setExpectedConnectionDevice(DEVICE3)
.setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 7e197fe..86d24f9 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -27,6 +27,7 @@
import android.content.ComponentName;
import android.net.Uri;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -123,6 +124,7 @@
mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
}
@Override
@@ -862,6 +864,7 @@
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
}
}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 3d06ad0..97405a3 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -16,26 +16,45 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.media.ToneGenerator;
import android.telecom.DisconnectCause;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseArray;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs;
+import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.DtmfLocalTonePlayer;
import com.android.server.telecom.InCallTonePlayer;
-import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
import com.android.server.telecom.RingbackPlayer;
import com.android.server.telecom.Ringer;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
@@ -50,22 +69,6 @@
import java.util.List;
import java.util.stream.Collectors;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class CallAudioManagerTest extends TelecomTestCase {
@Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
@@ -78,6 +81,8 @@
@Mock private BluetoothStateReceiver mBluetoothStateReceiver;
@Mock private TelecomSystem.SyncRoot mLock;
+ @Mock private FeatureFlags mFlags;
+
private CallAudioManager mCallAudioManager;
@Override
@@ -87,12 +92,13 @@
doAnswer((invocation) -> {
InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
doAnswer((invocation2) -> {
- mCallAudioManager.setIsTonePlaying(true);
+ mCallAudioManager.setIsTonePlaying(invocation.getArgument(0), true);
return true;
}).when(mockInCallTonePlayer).startTone();
return mockInCallTonePlayer;
- }).when(mPlayerFactory).createPlayer(anyInt());
+ }).when(mPlayerFactory).createPlayer(any(Call.class), anyInt());
when(mCallsManager.getLock()).thenReturn(mLock);
+ when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(true);
mCallAudioManager = new CallAudioManager(
mCallAudioRouteStateMachine,
mCallsManager,
@@ -101,7 +107,8 @@
mRinger,
mRingbackPlayer,
mBluetoothStateReceiver,
- mDtmfLocalTonePlayer);
+ mDtmfLocalTonePlayer,
+ mFlags);
}
@Override
@@ -204,7 +211,7 @@
assertMessageArgEquality(correctArgs, captor.getValue());
disconnectCall(call);
- stopTone();
+ stopTone(call);
mCallAudioManager.onCallRemoved(call);
verifyProperCleanup();
@@ -241,7 +248,7 @@
mCallAudioManager.onCallStateChanged(call, CallState.ANSWERED, CallState.ACTIVE);
disconnectCall(call);
- stopTone();
+ stopTone(call);
mCallAudioManager.onCallRemoved(call);
verifyProperCleanup();
@@ -277,25 +284,40 @@
verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
assertMessageArgEquality(expectedArgs, captor.getValue());
- verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
- anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
-
+ if (mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+ // Expect another invocation due to audio mode change signal.
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ } else {
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ }
when(call.getState()).thenReturn(CallState.ACTIVE);
mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
assertMessageArgEquality(expectedArgs, captor.getValue());
- verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
- anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
-
+ if (mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+ verify(mCallAudioModeStateMachine, times(4)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ } else {
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ }
disconnectCall(call);
- stopTone();
+ stopTone(call);
mCallAudioManager.onCallRemoved(call);
verifyProperCleanup();
}
+ @Test
+ public void testSingleOutgoingCallWithoutAudioModeUpdateOnForegroundCallChange() {
+ when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(false);
+ testSingleOutgoingCall();
+ }
+
@MediumTest
@Test
public void testRingbackStartStop() {
@@ -327,8 +349,14 @@
verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
assertMessageArgEquality(expectedArgs, captor.getValue());
- verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
- anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ if (mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+ // Expect an extra time due to audio mode change signal
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ } else {
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+ }
// Ensure we started ringback.
verify(mRingbackPlayer).startRingbackForCall(any(Call.class));
@@ -350,6 +378,12 @@
verify(mRingbackPlayer, times(1)).startRingbackForCall(any(Call.class));
}
+ @Test
+ public void testRingbackStartStopWithoutAudioModeUpdateOnForegroundCallChange() {
+ when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(false);
+ testRingbackStartStop();
+ }
+
@SmallTest
@Test
public void testNewCallGoesToAudioProcessing() {
@@ -495,7 +529,7 @@
mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
CallState.DISCONNECTED);
- verify(mPlayerFactory, never()).createPlayer(anyInt());
+ verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
CallAudioModeStateMachine.MessageArgs expectedArgs2 = new Builder()
.setHasActiveOrDialingCalls(false)
.setHasRingingCalls(false)
@@ -526,7 +560,7 @@
mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
CallState.SIMULATED_RINGING);
- verify(mPlayerFactory, never()).createPlayer(anyInt());
+ verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
.setHasActiveOrDialingCalls(false)
.setHasRingingCalls(true)
@@ -555,7 +589,7 @@
mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
CallState.ACTIVE);
- verify(mPlayerFactory, never()).createPlayer(anyInt());
+ verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
.setHasActiveOrDialingCalls(true)
.setHasRingingCalls(false)
@@ -584,7 +618,7 @@
mCallAudioManager.onCallStateChanged(call, CallState.SIMULATED_RINGING,
CallState.ACTIVE);
- verify(mPlayerFactory, never()).createPlayer(anyInt());
+ verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
.setHasActiveOrDialingCalls(true)
.setHasRingingCalls(false)
@@ -643,7 +677,7 @@
mCallAudioManager.onCallStateChanged(call, CallState.SIMULATED_RINGING,
CallState.DISCONNECTED);
- verify(mPlayerFactory, never()).createPlayer(anyInt());
+ verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
CallAudioModeStateMachine.MessageArgs expectedArgs2 = new Builder()
.setHasActiveOrDialingCalls(false)
.setHasRingingCalls(false)
@@ -702,6 +736,77 @@
assertFalse(captor.getValue().isStreaming);
}
+ @SmallTest
+ @Test
+ public void testTriggerAudioManagerModeChange() {
+ if (!mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+ // Skip if the new behavior isn't in use.
+ return;
+ }
+ // Start with an incoming PSTN call
+ Call pstnCall = mock(Call.class);
+ when(pstnCall.getState()).thenReturn(CallState.RINGING);
+ when(pstnCall.getIsVoipAudioMode()).thenReturn(false);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ // Add the call
+ mCallAudioManager.onCallAdded(pstnCall);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .setForegroundCallIsVoip(false)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls
+
+ // Make call active; don't expect there to be an audio mode transition.
+ when(pstnCall.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(pstnCall, CallState.RINGING, CallState.ACTIVE);
+ verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE),
+ any(CallAudioModeStateMachine.MessageArgs.class));
+ clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls
+
+ // Add a new Voip call in ringing state; this should not result in a direct audio mode
+ // change.
+ Call voipCall = mock(Call.class);
+ when(voipCall.getState()).thenReturn(CallState.RINGING);
+ when(voipCall.getIsVoipAudioMode()).thenReturn(true);
+ mCallAudioManager.onCallAdded(voipCall);
+ verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE),
+ any(CallAudioModeStateMachine.MessageArgs.class));
+ clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls
+
+ // Make voip call active and set the PSTN call to locally disconnecting; the new foreground
+ // call will be the voip call.
+ when(pstnCall.isLocallyDisconnecting()).thenReturn(true);
+ when(voipCall.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(voipCall, CallState.RINGING, CallState.ACTIVE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs2 =
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .setForegroundCallIsVoip(true)
+ .build();
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+ }
+
private Call createSimulatedRingingCall() {
Call call = mock(Call.class);
when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
@@ -765,7 +870,7 @@
"", "", "", ToneGenerator.TONE_PROP_PROMPT));
mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
- verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+ verify(mPlayerFactory).createPlayer(any(Call.class), eq(InCallTonePlayer.TONE_CALL_ENDED));
correctArgs = new Builder()
.setHasActiveOrDialingCalls(false)
.setHasRingingCalls(false)
@@ -782,10 +887,10 @@
assertMessageArgEquality(correctArgs, captor.getValue());
}
- private void stopTone() {
+ private void stopTone(Call call) {
ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
- mCallAudioManager.setIsTonePlaying(false);
+ mCallAudioManager.setIsTonePlaying(call, false);
CallAudioModeStateMachine.MessageArgs correctArgs = new Builder()
.setHasActiveOrDialingCalls(false)
.setHasRingingCalls(false)
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index d7854a5..4513c65 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -16,14 +16,32 @@
package com.android.server.telecom.tests;
+import static com.android.server.telecom.CallAudioModeStateMachine.CALL_AUDIO_FOCUS_REQUEST;
+import static com.android.server.telecom.CallAudioModeStateMachine.RING_AUDIO_FOCUS_REQUEST;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.HandlerThread;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
-import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
+import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.SystemStateHelper;
import org.junit.After;
@@ -31,18 +49,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-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.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class CallAudioModeStateMachineTest extends TelecomTestCase {
private static final int TEST_TIMEOUT = 1000;
@@ -51,6 +60,7 @@
@Mock private AudioManager mAudioManager;
@Mock private CallAudioManager mCallAudioManager;
@Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
private HandlerThread mTestThread;
@@ -60,8 +70,9 @@
mTestThread = new HandlerThread("CallAudioModeStateMachineTest");
mTestThread.start();
super.setUp();
- when(mCallAudioManager.getCallAudioRouteStateMachine())
+ when(mCallAudioManager.getCallAudioRouteAdapter())
.thenReturn(mCallAudioRouteStateMachine);
+ when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
}
@Override
@@ -76,7 +87,7 @@
@Test
public void testNoFocusWhenRingerSilenced() throws Throwable {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -108,7 +119,7 @@
@Test
public void testSwitchToStreamingMode() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -138,7 +149,7 @@
@Test
public void testExitStreamingMode() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ENTER_STREAMING_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -166,7 +177,7 @@
@Test
public void testNoRingWhenDeviceIsAtEar() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
sm.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL, new Builder()
@@ -202,7 +213,7 @@
@Test
public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -246,7 +257,7 @@
@Test
public void testDoNotRingTwiceWhenHfpConnected() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -284,7 +295,7 @@
@Test
public void testStartRingingAfterHfpConnectedIfNotAlreadyPlaying() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -318,7 +329,46 @@
verify(mCallAudioManager, times(2)).startRinging();
}
+ @SmallTest
+ @Test
+ public void testAudioFocusRequestWithResolveHiddenDependencies() {
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
+ when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
+ ArgumentCaptor<AudioFocusRequest> captor = ArgumentCaptor.forClass(AudioFocusRequest.class);
+ sm.setCallAudioManager(mCallAudioManager);
+
+ resetMocks();
+ when(mCallAudioManager.startRinging()).thenReturn(true);
+ when(mCallAudioManager.isRingtonePlaying()).thenReturn(false);
+
+ sm.sendMessage(CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ verify(mAudioManager).requestAudioFocus(captor.capture());
+ assertTrue(areAudioFocusRequestsMatch(captor.getValue(), RING_AUDIO_FOCUS_REQUEST));
+
+ sm.sendMessage(CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ verify(mAudioManager, atLeast(1)).requestAudioFocus(captor.capture());
+ AudioFocusRequest request = captor.getValue();
+ assertTrue(areAudioFocusRequestsMatch(request, CALL_AUDIO_FOCUS_REQUEST));
+
+ sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+ }
+
private void resetMocks() {
clearInvocations(mCallAudioManager, mAudioManager);
}
+
+ private boolean areAudioFocusRequestsMatch(AudioFocusRequest r1, AudioFocusRequest r2) {
+ if ((r1 == null) || (r2 == null)) {
+ return false;
+ }
+
+ if (r1.getFocusGain() != r2.getFocusGain()) {
+ return false;
+ }
+
+ return r1.getAudioAttributes().equals(r2.getAudioAttributes());
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
index c7e5aa9..844a216 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
@@ -16,10 +16,22 @@
package com.android.server.telecom.tests;
+
+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;
+import static org.mockito.Mockito.when;
+
import android.media.AudioManager;
import android.os.HandlerThread;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs;
@@ -36,15 +48,6 @@
import java.util.Collection;
import java.util.List;
-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;
-import static org.mockito.Mockito.when;
-
@RunWith(Parameterized.class)
public class CallAudioModeTransitionTests extends TelecomTestCase {
private static class ModeTestParameters {
@@ -103,6 +106,7 @@
@Mock private SystemStateHelper mSystemStateHelper;
@Mock private AudioManager mAudioManager;
@Mock private CallAudioManager mCallAudioManager;
+ @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
private final ModeTestParameters mParams;
private HandlerThread mTestThread;
@@ -130,13 +134,14 @@
@SmallTest
public void modeTransitionTest() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper());
+ mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(mParams.initialAudioState);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
resetMocks();
when(mCallAudioManager.startRinging()).thenReturn(true);
+ when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
if (mParams.initialAudioState
== CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING) {
when(mAudioManager.getMode())
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
index dfe1483..79247be 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
@@ -24,8 +24,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
+import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.DockManager;
@@ -47,6 +48,7 @@
@Mock private BluetoothRouteManager mBluetoothRouteManager;
@Mock private WiredHeadsetManager mWiredHeadsetManager;
@Mock private DockManager mDockManager;
+ @Mock private AsyncRingtonePlayer mRingtonePlayer;
@Override
@Before
@@ -57,7 +59,8 @@
mCallAudioRouteStateMachine,
mBluetoothRouteManager,
mWiredHeadsetManager,
- mDockManager);
+ mDockManager,
+ mRingtonePlayer);
}
@Override
@@ -126,6 +129,16 @@
mAdapter.onBluetoothAudioConnected();
verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ verify(mRingtonePlayer).updateBtActiveState(true);
+ }
+
+ @SmallTest
+ @Test
+ public void testOnBluetoothAudioConnecting() {
+ mAdapter.onBluetoothAudioConnecting();
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ verify(mRingtonePlayer).updateBtActiveState(false);
}
@SmallTest
@@ -134,6 +147,7 @@
mAdapter.onBluetoothAudioDisconnected();
verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
+ verify(mRingtonePlayer).updateBtActiveState(false);
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 569c487..d2da505 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -16,6 +16,26 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -26,18 +46,20 @@
import android.media.IAudioService;
import android.os.HandlerThread;
import android.telecom.CallAudioState;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.StatusBarNotifier;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import org.junit.After;
import org.junit.Before;
@@ -50,6 +72,7 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -57,24 +80,6 @@
import java.util.List;
import java.util.Set;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class CallAudioRouteStateMachineTest extends TelecomTestCase {
@@ -94,12 +99,14 @@
@Mock Call fakeSelfManagedCall;
@Mock Call fakeCall;
@Mock CallAudioManager mockCallAudioManager;
+ @Mock BluetoothDevice mockWatchDevice;
private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
private static final int TEST_TIMEOUT = 500;
private AudioManager mockAudioManager;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
private HandlerThread mThreadHandler;
+ CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Override
@Before
@@ -110,6 +117,8 @@
mThreadHandler.start();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+ mCommunicationDeviceTracker.setBluetoothRouteManager(mockBluetoothRouteManager);
mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
@Override
@@ -129,9 +138,12 @@
when(fakeSelfManagedCall.isAlive()).thenReturn(true);
when(fakeSelfManagedCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
when(fakeSelfManagedCall.isSelfManaged()).thenReturn(true);
+ when(mFeatureFlags.transitRouteBeforeAudioDisconnectBt()).thenReturn(false);
doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
any(CallAudioState.class));
+ when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(false);
}
@Override
@@ -154,7 +166,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
// Since we don't know if we're on a platform with an earpiece or not, all we can do
// is ensure the stateMachine construction didn't fail. But at least we exercised the
@@ -174,7 +188,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -197,8 +213,8 @@
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// assert expected end state
assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
@@ -208,6 +224,112 @@
.onCallAudioStateChanged(any(), any());
}
+ @SmallTest
+ @Test
+ public void testSystemAudioStateIsNotUpdatedFlagOff() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
+ when(mockCallsManager.getTrackedCalls()).thenReturn(trackedCalls);
+ when(mFeatureFlags.availableRoutesNeverUpdatedAfterSetSystemAudioState()).thenReturn(false);
+
+ // start state --> ROUTE_EARPIECE
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ stateMachine.initialize(initState);
+
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+ CallAudioRouteStateMachine.ROUTE_EARPIECE);
+
+ // ROUTE_EARPIECE --> ROUTE_SPEAKER
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+ CallAudioRouteStateMachine.SPEAKER_ON);
+
+ stateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+ waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+ CallAudioState expectedCallAudioState = stateMachine.getLastKnownCallAudioState();
+
+ // assert expected end state
+ assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+ CallAudioRouteStateMachine.ROUTE_SPEAKER);
+ // should update the audio route on all tracked calls ...
+ verify(mockConnectionServiceWrapper, times(trackedCalls.size()))
+ .onCallAudioStateChanged(any(), any());
+
+ assertNotEquals(expectedCallAudioState, stateMachine.getCurrentCallAudioState());
+ }
+
+ @SmallTest
+ @Test
+ public void testSystemAudioStateIsUpdatedFlagOn() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
+ when(mockCallsManager.getTrackedCalls()).thenReturn(trackedCalls);
+ when(mFeatureFlags.availableRoutesNeverUpdatedAfterSetSystemAudioState()).thenReturn(true);
+
+ // start state --> ROUTE_EARPIECE
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ stateMachine.initialize(initState);
+
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+ CallAudioRouteStateMachine.ROUTE_EARPIECE);
+
+ // ROUTE_EARPIECE --> ROUTE_SPEAKER
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+ CallAudioRouteStateMachine.SPEAKER_ON);
+
+ stateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ CallAudioState expectedCallAudioState = stateMachine.getLastKnownCallAudioState();
+
+ // assert expected end state
+ assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+ CallAudioRouteStateMachine.ROUTE_SPEAKER);
+ // should update the audio route on all tracked calls ...
+ verify(mockConnectionServiceWrapper, times(trackedCalls.size()))
+ .onCallAudioStateChanged(any(), any());
+
+ assertEquals(expectedCallAudioState, stateMachine.getCurrentCallAudioState());
+ }
+
@MediumTest
@Test
public void testStreamRingMuteChange() {
@@ -220,7 +342,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -250,6 +374,7 @@
foundValid = true;
}
assertTrue(foundValid);
+ verify(mockBluetoothRouteManager, timeout(1000L)).getBluetoothAudioConnectedDevice();
}
@MediumTest
@@ -264,7 +389,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -287,14 +414,14 @@
CallAudioState expectedMiddleState = new CallAudioState(false,
CallAudioState.ROUTE_WIRED_HEADSET,
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verifyNewSystemCallAudioState(initState, expectedMiddleState);
resetMocks();
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verifyNewSystemCallAudioState(expectedMiddleState, initState);
}
@@ -310,7 +437,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -331,7 +460,7 @@
CallAudioState.ROUTE_EARPIECE,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verifyNewSystemCallAudioState(initState, expectedEndState);
resetMocks();
stateMachine.sendMessageWithSessionInfo(
@@ -339,7 +468,7 @@
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
}
@@ -355,7 +484,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
@@ -380,12 +511,12 @@
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
null, availableDevices);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verifyNewSystemCallAudioState(initState, expectedMidState);
// clear out the handler state before resetting mocks in order to avoid introducing a
// CallAudioState that has a null list of supported BT devices
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
resetMocks();
// Now, switch back to BT explicitly
@@ -403,9 +534,9 @@
CallAudioState.ROUTE_BLUETOOTH,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
bluetoothDevice1, availableDevices);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// second wait needed for the BT_AUDIO_CONNECTED message
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verifyNewSystemCallAudioState(expectedMidState, expectedEndState);
stateMachine.sendMessageWithSessionInfo(
@@ -415,9 +546,9 @@
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// second wait needed for the BT_AUDIO_CONNECTED message
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// Verify that we're still on bluetooth.
assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
}
@@ -434,7 +565,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -449,13 +582,13 @@
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
CallAudioRouteStateMachine.RINGING_FOCUS);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
CallAudioRouteStateMachine.ACTIVE_FOCUS);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(nullable(String.class));
}
@@ -471,7 +604,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
setInBandRing(false);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -485,7 +620,7 @@
CallAudioRouteStateMachine.RINGING_FOCUS);
// Wait for the state machine to finish transiting to ActiveEarpiece before hooking up
// bluetooth mocks
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
when(mockBluetoothRouteManager.getConnectedDevices())
@@ -494,7 +629,7 @@
CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
CallAudioState expectedEndState = new CallAudioState(false,
@@ -505,14 +640,17 @@
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
CallAudioRouteStateMachine.ACTIVE_FOCUS);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
.thenReturn(bluetoothDevice1);
stateMachine.sendMessage(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- verify(mockCallAudioManager, times(1)).onRingerModeChange();
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ // It is possible that this will be called twice from ActiveBluetoothRoute#enter. The extra
+ // call to setBluetoothOn will trigger BT_AUDIO_CONNECTED, which also ends up invoking
+ // CallAudioManager#onRingerModeChange.
+ verify(mockCallAudioManager, atLeastOnce()).onRingerModeChange();
}
@SmallTest
@@ -527,7 +665,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -553,17 +693,80 @@
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH,
0, bluetoothDevice2.getAddress());
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verify(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress());
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
CallAudioState expectedEndState = new CallAudioState(false,
CallAudioState.ROUTE_BLUETOOTH,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
bluetoothDevice2,
availableDevices);
- verifyNewSystemCallAudioState(initState, expectedEndState);
+ assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
+ }
+
+ @SmallTest
+ @Test
+ public void testCallDisconnectedWhenAudioRoutedToBluetooth() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+ List<BluetoothDevice> availableDevices = Arrays.asList(bluetoothDevice1);
+
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+ when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+ when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+ when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices);
+ when(mockBluetoothRouteManager.isInbandRingingEnabled()).thenReturn(true);
+ when(mFeatureFlags.transitRouteBeforeAudioDisconnectBt()).thenReturn(true);
+ doAnswer(invocation -> {
+ when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+ .thenReturn(bluetoothDevice1);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ return null;
+ }).when(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice1.getAddress());
+ doAnswer(invocation -> {
+ when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+ .thenReturn(bluetoothDevice1);
+ stateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
+ return null;
+ }).when(mockBluetoothRouteManager).disconnectAudio();
+
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, null,
+ availableDevices);
+ stateMachine.initialize(initState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.NO_FOCUS, bluetoothDevice1.getAddress());
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ verify(mockBluetoothRouteManager).disconnectAudio();
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ CallAudioState expectedEndState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
+ bluetoothDevice1,
+ availableDevices);
+
+ assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
}
@SmallTest
@@ -578,7 +781,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -587,13 +792,13 @@
// Raise a dock connect event.
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_DOCK);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
assertTrue(!stateMachine.isInActiveState());
verify(mockAudioManager, never()).setSpeakerphoneOn(eq(true));
// Raise a dock disconnect event.
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.DISCONNECT_DOCK);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
assertTrue(!stateMachine.isInActiveState());
verify(mockAudioManager, never()).setSpeakerphoneOn(eq(false));
}
@@ -610,7 +815,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -622,7 +829,7 @@
// Switch to active, pretending that a call came in.
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
CallAudioRouteStateMachine.ACTIVE_FOCUS);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// Make sure that we've successfully switched to the active speaker route and that we've
// called setSpeakerOn
@@ -645,7 +852,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -667,7 +876,7 @@
// Switch to active, pretending that a call came in.
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
CallAudioRouteStateMachine.ACTIVE_FOCUS);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// Make sure that we've successfully switched to the active BT route and that we've
// called connectAudio on the right device.
@@ -678,6 +887,112 @@
@SmallTest
@Test
+ public void testSetAndClearEarpieceCommunicationDevice() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ AudioDeviceInfo earpiece = mock(AudioDeviceInfo.class);
+ when(earpiece.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ when(earpiece.getAddress()).thenReturn("");
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ devices.add(earpiece);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(eq(earpiece)))
+ .thenReturn(true);
+ when(mockAudioManager.getCommunicationDevice()).thenReturn(earpiece);
+
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER |
+ CallAudioState.ROUTE_WIRED_HEADSET);
+ stateMachine.initialize(initState);
+
+ // Switch to active
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ // Make sure that we've successfully switched to the active earpiece and that we set the
+ // communication device.
+ assertTrue(stateMachine.isInActiveState());
+ ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+ AudioDeviceInfo.class);
+ verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+ assertEquals(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+ infoArgumentCaptor.getValue().getType());
+
+ // Route earpiece to speaker
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+ CallAudioRouteStateMachine.SPEAKER_ON);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ // Assert that communication device was cleared
+ verify(mockAudioManager).clearCommunicationDevice();
+ }
+
+ @SmallTest
+ @Test
+ public void testSetAndClearWiredHeadsetCommunicationDevice() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ verifySetAndClearHeadsetCommunicationDevice(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ }
+
+ @SmallTest
+ @Test
+ public void testSetAndClearUsbHeadsetCommunicationDevice() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ verifySetAndClearHeadsetCommunicationDevice(AudioDeviceInfo.TYPE_USB_HEADSET);
+ }
+
+ @SmallTest
+ @Test
+ public void testActiveFocusRouteSwitchFromQuiescentBluetooth() {
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ // Start the route in quiescent and ensure that a switch to ACTIVE_FOCUS transitions to
+ // the corresponding active route even when there aren't any active BT devices available.
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE);
+ stateMachine.initialize(initState);
+
+ // Switch to active
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ // Make sure that we've successfully switched to the active route on BT
+ assertTrue(stateMachine.isInActiveState());
+ }
+
+ @SmallTest
+ @Test
public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -761,7 +1076,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
@@ -778,7 +1095,9 @@
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -789,15 +1108,206 @@
CallAudioState expectedEndState = new CallAudioState(false,
CallAudioState.ROUTE_STREAMING, CallAudioState.ROUTE_STREAMING);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
verifyNewSystemCallAudioState(initState, expectedEndState);
resetMocks();
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
assertEquals(initState, stateMachine.getCurrentCallAudioState());
}
+ @SmallTest
+ @Test
+ public void testIgnoreSpeakerOffMessage() {
+ when(mockBluetoothRouteManager.isInbandRingingEnabled()).thenReturn(true);
+ when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+ .thenReturn(bluetoothDevice1);
+ when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH);
+ stateMachine.initialize(initState);
+
+ doAnswer(
+ (address) -> {
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SPEAKER_OFF);
+ stateMachine.sendMessageDelayed(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED,
+ 5000L);
+ return null;
+ }).when(mockBluetoothRouteManager).connectBluetoothAudio(anyString());
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);
+
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_EARPIECE);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ @MediumTest
+ @Test
+ public void testIgnoreImplicitBTSwitchWhenDeviceIsWatch() {
+ when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(true);
+ when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ AudioDeviceInfo headset = mock(AudioDeviceInfo.class);
+ when(headset.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(headset.getAddress()).thenReturn("");
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ devices.add(headset);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(eq(headset)))
+ .thenReturn(true);
+ when(mockAudioManager.getCommunicationDevice()).thenReturn(headset);
+
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_WIRED_HEADSET, CallAudioState.ROUTE_WIRED_HEADSET
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ stateMachine.initialize(initState);
+
+ // Switch to active
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ // Make sure that we've successfully switched to the active headset.
+ assertTrue(stateMachine.isInActiveState());
+ ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+ AudioDeviceInfo.class);
+ verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+ assertEquals(AudioDeviceInfo.TYPE_WIRED_HEADSET, infoArgumentCaptor.getValue().getType());
+
+ // Set up watch device as only available BT device.
+ Collection<BluetoothDevice> availableDevices = Collections.singleton(mockWatchDevice);
+
+ when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+ when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+ when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices);
+ when(mockBluetoothRouteManager.isWatch(any(BluetoothDevice.class))).thenReturn(true);
+
+ // Disconnect wired headset to force switch to BT (verify that we ignore the implicit switch
+ // to BT when the watch is the only connected device and that we move into the next
+ // available route.
+ stateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
+ null, availableDevices);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ @SmallTest
+ @Test
+ public void testQuiescentBluetoothRouteResetMute() {
+ when(mFeatureFlags.resetMuteWhenEnteringQuiescentBtRoute()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ stateMachine.initialize(initState);
+
+ // Switch to active and mute
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ assertTrue(stateMachine.isInActiveState());
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.MUTE_ON);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ CallAudioState expectedState = new CallAudioState(true,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.NO_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ expectedState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ // TODO: Re-enable this part of the test; this is now failing because we have to
+ // revert ag/23783145.
+ // assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ @SmallTest
+ @Test
+ public void testSupportRouteMaskUpdateWhenBtAudioConnected() {
+ when(mFeatureFlags.updateRouteMaskWhenBtConnected()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ stateMachine.initialize(initState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
private void initializationTestHelper(CallAudioState expectedState,
int earpieceControl) {
when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
@@ -816,7 +1326,9 @@
mAudioServiceFactory,
earpieceControl,
mThreadHandler.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
@@ -856,4 +1368,59 @@
doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
any(CallAudioState.class));
}
+
+ private void verifySetAndClearHeadsetCommunicationDevice(int audioType) {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ AudioDeviceInfo headset = mock(AudioDeviceInfo.class);
+ when(headset.getType()).thenReturn(audioType);
+ when(headset.getAddress()).thenReturn("");
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ devices.add(headset);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(eq(headset)))
+ .thenReturn(true);
+ when(mockAudioManager.getCommunicationDevice()).thenReturn(headset);
+
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_EARPIECE);
+ stateMachine.initialize(initState);
+
+ // Switch to active
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ // Make sure that we've successfully switched to the active headset and that we set the
+ // communication device.
+ assertTrue(stateMachine.isInActiveState());
+ ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+ AudioDeviceInfo.class);
+ verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+ assertEquals(audioType, infoArgumentCaptor.getValue().getType());
+
+ // Route out of headset route
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ // Assert that communication device was cleared
+ verify(mockAudioManager).clearCommunicationDevice();
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index cf684de..6b9b5c8 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -20,10 +20,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -37,9 +37,11 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.telecom.CallAudioState;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallsManager;
@@ -155,6 +157,7 @@
@Mock StatusBarNotifier mockStatusBarNotifier;
@Mock Call fakeCall;
@Mock CallAudioManager mockCallAudioManager;
+ private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
private static final int TEST_TIMEOUT = 500;
private AudioManager mockAudioManager;
@@ -174,6 +177,8 @@
mHandlerThread.start();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+ mCommunicationDeviceTracker.setBluetoothRouteManager(mockBluetoothRouteManager);
mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
@Override
@@ -270,7 +275,9 @@
mAudioServiceFactory,
mParams.earpieceControl,
mHandlerThread.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
setupMocksForParams(stateMachine, mParams);
@@ -288,17 +295,17 @@
if (mParams.initialRoute == CallAudioState.ROUTE_BLUETOOTH) {
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
}
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
// Clear invocations on mocks to discard stuff from initialization
clearInvocations();
sendActionToStateMachine(stateMachine);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
- Handler h = stateMachine.getHandler();
+ Handler h = stateMachine.getAdapterHandler();
waitForHandlerAction(h, TEST_TIMEOUT);
stateMachine.quitStateMachine();
@@ -311,7 +318,7 @@
break;
case ON:
if (mParams.expectedBluetoothDevice == null) {
- verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
+ verify(mockBluetoothRouteManager, atLeastOnce()).connectBluetoothAudio(null);
} else {
verify(mockBluetoothRouteManager).connectBluetoothAudio(
mParams.expectedBluetoothDevice.getAddress());
@@ -367,7 +374,9 @@
mAudioServiceFactory,
mParams.earpieceControl,
mHandlerThread.getLooper(),
- Runnable::run /** do async stuff sync for test purposes */);
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
stateMachine.setCallAudioManager(mockCallAudioManager);
// Set up bluetooth and speakerphone state
@@ -388,8 +397,8 @@
// Omit the focus-getting statement
sendActionToStateMachine(stateMachine);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
stateMachine.quitStateMachine();
diff --git a/tests/src/com/android/server/telecom/tests/CallControlTest.java b/tests/src/com/android/server/telecom/tests/CallControlTest.java
index 2613206..c69521a 100644
--- a/tests/src/com/android/server/telecom/tests/CallControlTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallControlTest.java
@@ -39,14 +39,7 @@
import java.util.UUID;
public class CallControlTest extends TelecomTestCase {
-
- private static final PhoneAccountHandle mHandle = new PhoneAccountHandle(
- new ComponentName("foo", "bar"), "1");
-
- @Mock
- private ICallControl mICallControl;
- @Mock
- private ClientTransactionalServiceRepository mRepository;
+ @Mock private ICallControl mICallControl;
private static final String CALL_ID_1 = UUID.randomUUID().toString();
@Override
@@ -64,15 +57,7 @@
@Test
public void testGetCallId() {
- CallControl control = new CallControl(CALL_ID_1, mICallControl, mRepository, mHandle);
+ CallControl control = new CallControl(CALL_ID_1, mICallControl);
assertEquals(CALL_ID_1, control.getCallId().toString());
}
-
- @Test
- public void testCallControlHitsIllegalStateException() {
- CallControl control = new CallControl(CALL_ID_1, null, mRepository, mHandle);
- assertThrows(IllegalStateException.class, () ->
- control.setInactive(Runnable::run, result -> {
- }));
- }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
index f4008aa..9101a19 100644
--- a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.tests;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -50,7 +51,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
@RunWith(JUnit4.class)
public class CallEndpointControllerTest extends TelecomTestCase {
@@ -81,6 +84,9 @@
availableBluetooth1);
private static final CallAudioState audioState7 = new CallAudioState(false,
CallAudioState.ROUTE_STREAMING, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+ private static final CallAudioState audioState8 = new CallAudioState(false,
+ CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_ALL, bluetoothDevice1,
+ availableBluetooth2);
private CallEndpointController mCallEndpointController;
@@ -177,6 +183,74 @@
verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
}
+ /**
+ * Ensure that {@link CallAudioManager#setAudioRoute(int, String)} is invoked when the user
+ * requests to switch to a bluetooth CallEndpoint. This is an edge case where bluetooth is not
+ * the current CallEndpoint but the CallAudioState shows the bluetooth device is
+ * active/available.
+ */
+ @Test
+ public void testSwitchFromEarpieceToBluetooth() {
+ // simulate an audio state where the EARPIECE is active but a bluetooth device is active.
+ mCallEndpointController.onCallAudioStateChanged(null, audioState8 /* Ear but BT active */);
+ CallEndpoint btEndpoint = mCallEndpointController.getAvailableEndpoints().stream()
+ .filter(e -> e.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH)
+ .toList().get(0); // get the only available BT endpoint
+
+ // verify the CallEndpointController shows EARPIECE active + BT endpoint is active device
+ assertEquals(CallEndpoint.TYPE_EARPIECE,
+ mCallEndpointController.getCurrentCallEndpoint().getEndpointType());
+ assertNotNull(btEndpoint);
+
+ // request an endpoint change from earpiece to the bluetooth
+ doReturn(audioState8).when(mCallAudioManager).getCallAudioState();
+ mCallEndpointController.requestCallEndpointChange(btEndpoint, mResultReceiver);
+
+ // verify the transaction was successful and CallAudioManager#setAudioRoute was called
+ verify(mResultReceiver, never()).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
+ verify(mCallAudioManager, times(1)).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+ eq(bluetoothDevice1.getAddress()));
+ }
+
+
+ /**
+ * Ensure that {@link CallAudioManager#setAudioRoute(int, String)} is invoked when the user
+ * requests to switch to from one bluetooth device to another.
+ */
+ @Test
+ public void testBtDeviceSwitch() {
+ // bluetoothDevice1 should start as active and bluetoothDevice2 is available
+ mCallEndpointController.onCallAudioStateChanged(null, audioState2 /* BT active D1 */);
+ CallEndpoint currentEndpoint = mCallEndpointController.getCurrentCallEndpoint();
+ List<CallEndpoint> btEndpoints = mCallEndpointController.getAvailableEndpoints().stream()
+ .filter(e -> e.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH)
+ .toList(); // get the only available BT endpoint
+
+ // verify the initial state of the test
+ assertEquals(2, btEndpoints.size());
+ assertEquals(CallEndpoint.TYPE_BLUETOOTH, currentEndpoint.getEndpointType());
+
+ CallEndpoint otherBluetoothEndpoint = null;
+ for (CallEndpoint e : btEndpoints) {
+ if (!e.equals(currentEndpoint)) {
+ otherBluetoothEndpoint = e;
+ }
+ }
+
+ assertNotNull(otherBluetoothEndpoint);
+ assertNotEquals(currentEndpoint, otherBluetoothEndpoint);
+
+ // request an endpoint change from BT D1 --> BT D2
+ doReturn(audioState2).when(mCallAudioManager).getCallAudioState();
+ mCallEndpointController.requestCallEndpointChange(otherBluetoothEndpoint, mResultReceiver);
+
+ // verify the transaction was successful and CallAudioManager#setAudioRoute was called
+ verify(mResultReceiver, never()).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
+ verify(mCallAudioManager, times(1))
+ .setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+ eq(bluetoothDevice2.getAddress()));
+ }
+
@Test
public void testAvailableEndpointChanged() throws Exception {
mCallEndpointController.onCallAudioStateChanged(audioState1, audioState6);
diff --git a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index cf44cfe..be8e6fb 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -27,10 +27,10 @@
import android.telecom.Connection;
import android.telecom.InCallService;
import android.telecom.ParcelableCall;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 9466220..fa35f25 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.tests;
+import static com.android.server.telecom.tests.TelecomSystemTest.TEST_TIMEOUT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -36,6 +38,7 @@
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.Country;
@@ -44,6 +47,7 @@
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.SystemClock;
@@ -59,12 +63,13 @@
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Analytics;
+import com.android.server.telecom.AnomalyReporterAdapter;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallLogManager;
import com.android.server.telecom.CallState;
@@ -72,6 +77,7 @@
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.TelephonyUtil;
+import com.android.server.telecom.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
@@ -85,6 +91,9 @@
import org.mockito.stubbing.Answer;
import java.util.Arrays;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
@RunWith(JUnit4.class)
public class CallLogManagerTest extends TelecomTestCase {
@@ -123,6 +132,11 @@
PhoneAccountRegistrar mMockPhoneAccountRegistrar;
@Mock
MissedCallNotifier mMissedCallNotifier;
+ @Mock
+ AnomalyReporterAdapter mAnomalyReporterAdapter;
+
+ @Mock
+ FeatureFlags mFeatureFlags;
@Override
@Before
@@ -130,7 +144,7 @@
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mCallLogManager = new CallLogManager(mContext, mMockPhoneAccountRegistrar,
- mMissedCallNotifier);
+ mMissedCallNotifier, mAnomalyReporterAdapter, mFeatureFlags);
mDefaultAccountHandle = new PhoneAccountHandle(
new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
TEST_PHONE_ACCOUNT_ID,
@@ -181,6 +195,9 @@
when(userManager.getUserInfo(eq(CURRENT_USER_ID))).thenReturn(userInfo);
when(userManager.getUserInfo(eq(OTHER_USER_ID))).thenReturn(otherUserInfo);
when(userManager.getUserInfo(eq(MANAGED_USER_ID))).thenReturn(managedProfileUserInfo);
+ PackageManager packageManager = mContext.getPackageManager();
+ when(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+ when(mFeatureFlags.telecomLogExternalWearableCalls()).thenReturn(false);
}
@Override
@@ -357,6 +374,7 @@
VIA_NUMBER_STRING, // viaNumber
null
);
+ when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(true);
mCallLogManager.onCallStateChanged(fakeIncomingCall, CallState.ACTIVE,
CallState.DISCONNECTED);
ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
@@ -366,7 +384,7 @@
@MediumTest
@Test
- public void testLogCallDirectionMissed() {
+ public void testLogCallDirectionMissedAddCallUriForMissedCallsFlagOff() {
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
.thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
Call fakeMissedCall = makeFakeCall(
@@ -382,6 +400,7 @@
VIA_NUMBER_STRING, // viaNumber
null
);
+ when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(false);
mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
CallState.DISCONNECTED);
@@ -390,7 +409,39 @@
Integer.valueOf(CallLog.Calls.MISSED_TYPE));
// Timeout needed because showMissedCallNotification is called from onPostExecute.
verify(mMissedCallNotifier, timeout(TEST_TIMEOUT_MILLIS))
- .showMissedCallNotification(any(MissedCallNotifier.CallInfo.class));
+ .showMissedCallNotification(any(MissedCallNotifier.CallInfo.class),
+ /* uri= */ eq(null));
+ }
+
+ @MediumTest
+ @Test
+ public void testLogCallDirectionMissedAddCallUriForMissedCallsFlagOn() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeMissedCall = makeFakeCall(
+ DisconnectCause.MISSED, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ VIA_NUMBER_STRING, // viaNumber
+ null
+ );
+ when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(true);
+
+ mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.MISSED_TYPE));
+ // Timeout needed because showMissedCallNotification is called from onPostExecute.
+ verify(mMissedCallNotifier, timeout(TEST_TIMEOUT_MILLIS))
+ .showMissedCallNotification(any(MissedCallNotifier.CallInfo.class),
+ /* uri= */ any(Uri.class));
}
@MediumTest
@@ -788,19 +839,34 @@
assertEquals(1, insertedValues.getAsInteger(Calls.IS_READ).intValue());
}
- @SmallTest
@Test
- public void testCountryIso_setCache() {
- Country testCountry = new Country(TEST_ISO, Country.COUNTRY_SOURCE_LOCALE);
- CountryDetector mockDetector = (CountryDetector) mContext.getSystemService(
- Context.COUNTRY_DETECTOR);
- when(mockDetector.detectCountry()).thenReturn(testCountry);
+ public void testLogCallWhenExternalCallOnWatch() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ PackageManager packageManager = mContext.getPackageManager();
+ when(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+ when(mFeatureFlags.telecomLogExternalWearableCalls()).thenReturn(true);
+ Call fakeMissedCall = makeFakeCall(
+ DisconnectCause.REJECTED, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ VIA_NUMBER_STRING, // viaNumber
+ null
+ );
+ when(fakeMissedCall.isExternalCall()).thenReturn(true);
- String resultIso = mCallLogManager.getCountryIso();
-
- verifyCountryIso(mockDetector, resultIso);
+ mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+ verifyInsertionWithCapture(CURRENT_USER_ID);
}
+
@SmallTest
@Test
public void testCountryIso_newCountryDetected() {
@@ -808,17 +874,28 @@
Country testCountry2 = new Country(TEST_ISO_2, Country.COUNTRY_SOURCE_LOCALE);
CountryDetector mockDetector = (CountryDetector) mContext.getSystemService(
Context.COUNTRY_DETECTOR);
- when(mockDetector.detectCountry()).thenReturn(testCountry);
- // Put TEST_ISO in the Cache
+ Handler handler = new Handler(Looper.getMainLooper());
+
+ String initialIso = mCallLogManager.getCountryIso();
+ assertEquals(Locale.getDefault().getCountry(), initialIso);
+
+ ArgumentCaptor<Consumer<Country>> capture = ArgumentCaptor.forClass(Consumer.class);
+ verify(mockDetector).registerCountryDetectorCallback(
+ any(Executor.class), capture.capture());
+ Consumer<Country> countryConsumer = capture.getValue();
+
+ countryConsumer.accept(testCountry);
+ waitForHandlerAction(handler, TEST_TIMEOUT);
String resultIso = mCallLogManager.getCountryIso();
- ArgumentCaptor<CountryListener> captor = verifyCountryIso(mockDetector, resultIso);
+ assertEquals(TEST_ISO, resultIso);
- // Change ISO to TEST_ISO_2
- CountryListener listener = captor.getValue();
- listener.onCountryDetected(testCountry2);
-
- String resultIso2 = mCallLogManager.getCountryIso();
- assertEquals(TEST_ISO_2, resultIso2);
+ // If default locale is equal to TEST_ISO, test another ISO to assure working functionality.
+ if (initialIso.equals(TEST_ISO)) {
+ countryConsumer.accept(testCountry2);
+ waitForHandlerAction(handler, TEST_TIMEOUT);
+ resultIso = mCallLogManager.getCountryIso();
+ assertEquals(TEST_ISO_2, resultIso);
+ }
}
@SmallTest
@@ -893,6 +970,56 @@
@SmallTest
@Test
+ public void testDoNotLogCallExtra() {
+ when(mFeatureFlags.telecomSkipLogBasedOnExtra()).thenReturn(true);
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.LOCAL, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ VIA_NUMBER_STRING, // viaNumber
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ Bundle extras = new Bundle();
+ extras.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ when(fakeCall.getExtras()).thenReturn(extras);
+
+ assertFalse(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+ false /* isCanceled */));
+ }
+
+ @SmallTest
+ @Test
+ public void testIgnoresDoNotLogCallExtra_whenFlagDisabled() {
+ when(mFeatureFlags.telecomSkipLogBasedOnExtra()).thenReturn(false);
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.LOCAL, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ VIA_NUMBER_STRING, // viaNumber
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ Bundle extras = new Bundle();
+ extras.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ when(fakeCall.getExtras()).thenReturn(extras);
+
+ assertTrue(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+ false /* isCanceled */));
+ }
+
+ @SmallTest
+ @Test
public void testDoNotLogConferenceWithChildren() {
Call fakeCall = makeFakeCall(
DisconnectCause.LOCAL, // disconnectCauseCode
diff --git a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
index b5c6468..60952d3 100644
--- a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
@@ -28,7 +28,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,11 +40,11 @@
import android.media.AudioRecordingConfiguration;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
-import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.telecom.PhoneAccountHandle;
-import android.test.suitebuilder.annotation.MediumTest;
+
+import androidx.test.filters.MediumTest;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.telecom.Call;
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index 01446d1..8210686 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -55,10 +55,10 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index 4d8d497..d1427db 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -43,7 +43,8 @@
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 997e7dd..e06938d 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -21,7 +21,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
@@ -34,10 +33,12 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.UserHandle;
import android.telecom.CallAttributes;
import android.telecom.CallerInfo;
import android.telecom.Connection;
@@ -50,10 +51,10 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CallQuality;
-import android.test.suitebuilder.annotation.SmallTest;
import android.widget.Toast;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallIdMapper;
@@ -117,6 +118,7 @@
doReturn(new ComponentName(mContext, CallTest.class))
.when(mMockConnectionService).getComponentName();
doReturn(mMockToast).when(mMockToastProxy).makeText(any(), anyInt(), anyInt());
+ doReturn(UserHandle.CURRENT).when(mMockCallsManager).getCurrentUserHandle();
}
@After
@@ -200,7 +202,8 @@
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
// To start with connection creation isn't complete.
assertFalse(call.isCreateConnectionComplete());
@@ -338,7 +341,8 @@
false /* shouldAttachToExistingConnection*/,
true /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
assertFalse(call.wasDndCheckComputedForCall());
assertFalse(call.isCallSuppressedByDoNotDisturb());
@@ -364,7 +368,8 @@
false /* shouldAttachToExistingConnection*/,
true /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
assertNull(call.getConnectionServiceWrapper());
assertFalse(call.isTransactionalCall());
@@ -394,7 +399,8 @@
false /* shouldAttachToExistingConnection*/,
true /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
// setup
call.setIsTransactionalCall(true);
@@ -728,6 +734,52 @@
}));
}
+ @Test
+ @SmallTest
+ public void testExcludesInCallServiceFromDoNotLogCallExtra() {
+ Call call = createCall("any");
+ Bundle extra = new Bundle();
+ extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+
+ call.putInCallServiceExtras(extra, "packageName");
+
+ assertFalse(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+ }
+
+ @Test
+ @SmallTest
+ public void testExcludesConnectionServiceWithoutModifyStatePermissionFromDoNotLogCallExtra() {
+ PackageManager packageManager = mContext.getPackageManager();
+ Bundle extra = new Bundle();
+ extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ String packageName = SIM_1_HANDLE.getComponentName().getPackageName();
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(packageManager)
+ .checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+ Call call = createCall("any");
+
+ call.putConnectionServiceExtras(extra);
+
+ assertFalse(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+ }
+
+ @Test
+ @SmallTest
+ public void testDoesNotExcludeConnectionServiceWithModifyStatePermissionFromDoNotLogCallExtra() {
+ String packageName = SIM_1_HANDLE.getComponentName().getPackageName();
+ Bundle extra = new Bundle();
+ extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ PackageManager packageManager = mContext.getPackageManager();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(packageManager)
+ .checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+ Call call = createCall("any");
+
+ call.putConnectionServiceExtras(extra);
+
+ assertTrue(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+ }
+
private Call createCall(String id) {
return createCall(id, Call.CALL_DIRECTION_UNDEFINED);
}
@@ -748,6 +800,7 @@
false,
false,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java b/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
index 7c001c0..614ef71 100644
--- a/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
@@ -17,11 +17,11 @@
package com.android.server.telecom.tests;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -32,13 +32,13 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.telecom.Logging.Session;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.InstrumentationRegistry;
-
import android.telecom.CallerInfo;
import android.telecom.CallerInfoAsyncQuery;
+import android.telecom.Logging.Session;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.ContactsAsyncHelper;
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 9f46336..be00125 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -44,6 +44,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.lang.Thread.sleep;
+
import android.Manifest;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -75,15 +77,18 @@
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneCapability;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import android.widget.Toast;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telecom.IConnectionService;
import com.android.server.telecom.AnomalyReporterAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAnomalyWatchdog;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -123,6 +128,8 @@
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallStreamingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
@@ -135,7 +142,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -276,8 +283,10 @@
@Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
@Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
@Mock private PhoneCapability mPhoneCapability;
+ @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Mock private CallStreamingNotification mCallStreamingNotification;
-
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;
private CallsManager mCallsManager;
@Override
@@ -296,8 +305,8 @@
when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
mCallEndpointController);
when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
- anyInt(), any())).thenReturn(mCallAudioRouteStateMachine);
- when(mCallAudioModeStateMachineFactory.create(any(), any()))
+ anyInt(), any(), any(), any())).thenReturn(mCallAudioRouteStateMachine);
+ when(mCallAudioModeStateMachineFactory.create(any(), any(), any(), any()))
.thenReturn(mCallAudioModeStateMachine);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
@@ -345,10 +354,15 @@
mAccessibilityManagerAdapter,
// Just do async tasks synchronously to support testing.
command -> command.run(),
+ // For call audio tasks
+ command -> command.run(),
mBlockedNumbersAdapter,
TransactionManager.getTestInstance(),
mEmergencyCallDiagnosticLogger,
- mCallStreamingNotification);
+ mCommunicationDeviceTracker,
+ mCallStreamingNotification,
+ mFeatureFlags,
+ (call, listener, context, timeoutsAdapter, lock) -> mIncomingCallFilterGraph);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -391,7 +405,8 @@
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
ongoingCall.setState(CallState.ACTIVE, "just cuz");
return ongoingCall;
}
@@ -1318,8 +1333,9 @@
@SmallTest
@Test
- public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
+ public void testDndFilterAppliesOfCallsWhenPhoneAccountRequestsSkipped() {
// GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
+ when(mFeatureFlags.skipFilterPhoneAccountPerformDndFilter()).thenReturn(true);
Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
Bundle extras = new Bundle();
extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
@@ -1339,7 +1355,35 @@
// WHEN the incoming call is successfully added.
mCallsManager.onSuccessfulIncomingCall(incomingCall);
- // THEN the incoming call is not using call filtering
+ // THEN the incoming call is still applying Dnd filter.
+ verify(incomingCall).setIsUsingCallFiltering(eq(true));
+ }
+
+ @SmallTest
+ @Test
+ public void testNoFilterAppliesOfCallsWhenFlagNotEnabled() {
+ // Flag is not enabled.
+ when(mFeatureFlags.skipFilterPhoneAccountPerformDndFilter()).thenReturn(false);
+ Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
+ 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_1_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 applying filter.
verify(incomingCall).setIsUsingCallFiltering(eq(false));
}
@@ -2396,7 +2440,7 @@
mCallsManager.onCallFilteringComplete(callSpy, result, false /* timeout */);
verify(mMissedCallNotifier).showMissedCallNotification(
- any(MissedCallNotifier.CallInfo.class));
+ any(MissedCallNotifier.CallInfo.class), /* uri= */ eq(null));
}
@Test
@@ -2500,6 +2544,32 @@
assertEquals(DEFAULT_CALL_SCREENING_APP, outgoingCall.getPostCallPackageName());
}
+ /**
+ * Verify the only call state set from calling onSuccessfulOutgoingCall is CallState.DIALING.
+ */
+ @SmallTest
+ @Test
+ public void testOutgoingCallStateIsSetToAPreviousStateAndIgnored() {
+ when(mFeatureFlags.fixAudioFlickerForOutgoingCalls()).thenReturn(true);
+ Call outgoingCall = addSpyCall(CallState.CONNECTING);
+ mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.NEW);
+ verify(outgoingCall, never()).setState(eq(CallState.NEW), any());
+ verify(outgoingCall, times(1)).setState(eq(CallState.DIALING), any());
+ }
+
+ /**
+ * Verify a ConnectionService can start the call in the active state and avoid the dialing state
+ */
+ @SmallTest
+ @Test
+ public void testOutgoingCallStateCanAvoidDialingAndGoStraightToActive() {
+ when(mFeatureFlags.fixAudioFlickerForOutgoingCalls()).thenReturn(true);
+ Call outgoingCall = addSpyCall(CallState.CONNECTING);
+ mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.ACTIVE);
+ verify(outgoingCall, never()).setState(eq(CallState.DIALING), any());
+ verify(outgoingCall, times(1)).setState(eq(CallState.ACTIVE), any());
+ }
+
@SmallTest
@Test
public void testRejectIncomingCallOnPAHInactive_SecondaryUser() throws Exception {
@@ -2509,9 +2579,7 @@
WORK_HANDLE.getUserHandle(), service);
UserManager um = mContext.getSystemService(UserManager.class);
- UserHandle newUser = new UserHandle(11);
- when(mCallsManager.getCurrentUserHandle()).thenReturn(newUser);
- when(um.isUserAdmin(eq(newUser.getIdentifier()))).thenReturn(false);
+ when(um.isUserAdmin(anyInt())).thenReturn(false);
when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(false);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
.thenReturn(WORK_ACCOUNT);
@@ -2527,14 +2595,17 @@
@Test
public void testRejectIncomingCallOnPAHInactive_ProfilePaused() throws Exception {
ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
- doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
- mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
- SIM_2_HANDLE.getUserHandle(), service);
+ doReturn(WORK_HANDLE.getComponentName()).when(service).getComponentName();
+ mCallsManager.addConnectionServiceRepositoryCache(WORK_HANDLE.getComponentName(),
+ WORK_HANDLE.getUserHandle(), service);
UserManager um = mContext.getSystemService(UserManager.class);
- when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+ when(um.isUserAdmin(anyInt())).thenReturn(true);
+ when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(true);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
+ .thenReturn(WORK_ACCOUNT);
Call newCall = mCallsManager.processIncomingCallIntent(
- SIM_2_HANDLE, new Bundle(), false);
+ WORK_HANDLE, new Bundle(), false);
verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
assertFalse(newCall.isInECBM());
@@ -2571,9 +2642,7 @@
when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(WORK_HANDLE)))
.thenReturn(true);
UserManager um = mContext.getSystemService(UserManager.class);
- UserHandle newUser = new UserHandle(11);
- when(mCallsManager.getCurrentUserHandle()).thenReturn(newUser);
- when(um.isUserAdmin(eq(newUser.getIdentifier()))).thenReturn(false);
+ when(um.isUserAdmin(anyInt())).thenReturn(false);
when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(false);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
.thenReturn(WORK_ACCOUNT);
@@ -2736,8 +2805,9 @@
public void testQueryCurrentLocationCheckOnReceiveResult() throws Exception {
ConnectionServiceWrapper service = new ConnectionServiceWrapper(
new ComponentName(mContext.getPackageName(),
- mContext.getPackageName().getClass().getName()),
- null, mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
+ mContext.getPackageName().getClass().getName()), null,
+ mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null,
+ mFeatureFlags);
CompletableFuture<String> resultFuture = new CompletableFuture<>();
try {
@@ -2966,11 +3036,9 @@
mCallsManager.createActionSetCallStateAndPerformAction(
call, CallState.DISCONNECTED, "");
- verify(sourceCall).onConnectionEvent(eq(Connection.EVENT_HANDOVER_FAILED), any());
verify(sourceCall).onHandoverFailed(
android.telecom.Call.Callback.HANDOVER_FAILURE_USER_REJECTED);
- verify(call).sendCallEvent(eq(android.telecom.Call.EVENT_HANDOVER_FAILED), any());
verify(call).markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_FAILED);
}
@@ -2981,12 +3049,8 @@
Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
when(call.getHandoverDestinationCall()).thenReturn(destinationCall);
when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_FROM_STARTED);
-
mCallsManager.createActionSetCallStateAndPerformAction(
call, CallState.DISCONNECTED, "");
-
- verify(destinationCall).sendCallEvent(
- eq(android.telecom.Call.EVENT_HANDOVER_SOURCE_DISCONNECTED), any());
}
@SmallTest
@@ -3000,11 +3064,8 @@
mCallsManager.createActionSetCallStateAndPerformAction(
call, CallState.DISCONNECTED, "");
- verify(call).onConnectionEvent(eq(Connection.EVENT_HANDOVER_COMPLETE), any());
verify(call).onHandoverComplete();
verify(call).markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_COMPLETE);
- verify(destinationCall).sendCallEvent(
- eq(android.telecom.Call.EVENT_HANDOVER_COMPLETE), any());
verify(destinationCall).onHandoverComplete();
}
@@ -3021,11 +3082,8 @@
mCallsManager.createActionSetCallStateAndPerformAction(
call, CallState.DISCONNECTED, "");
- verify(call).onConnectionEvent(eq(Connection.EVENT_HANDOVER_COMPLETE), any());
verify(call).onHandoverComplete();
verify(call).markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_COMPLETE);
- verify(destinationCall).sendCallEvent(
- eq(android.telecom.Call.EVENT_HANDOVER_COMPLETE), any());
verify(destinationCall).onHandoverComplete();
verify(otherCall).disconnect();
}
@@ -3280,8 +3338,8 @@
// Mocks some methods to not call the real method.
doNothing().when(callSpy).unhold();
doNothing().when(callSpy).hold();
- doNothing().when(callSpy).answer(Matchers.anyInt());
- doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+ doNothing().when(callSpy).answer(ArgumentMatchers.anyInt());
+ doNothing().when(callSpy).setStartWithSpeakerphoneOn(ArgumentMatchers.anyBoolean());
mCallsManager.addCall(callSpy);
return callSpy;
@@ -3295,8 +3353,8 @@
doNothing().when(callSpy).unhold();
doNothing().when(callSpy).hold();
doNothing().when(callSpy).disconnect();
- doNothing().when(callSpy).answer(Matchers.anyInt());
- doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+ doNothing().when(callSpy).answer(ArgumentMatchers.anyInt());
+ doNothing().when(callSpy).setStartWithSpeakerphoneOn(ArgumentMatchers.anyBoolean());
return callSpy;
}
@@ -3321,7 +3379,8 @@
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
ongoingCall.setState(initialState, "just cuz");
if (targetPhoneAccount == SELF_MANAGED_HANDLE
|| targetPhoneAccount == SELF_MANAGED_2_HANDLE) {
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index cc22de2..54aaa4c 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import com.android.server.telecom.flags.FeatureFlags;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
@@ -27,6 +28,8 @@
import org.mockito.stubbing.Answer;
import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.StatusBarManager;
@@ -55,6 +58,7 @@
import android.location.LocationManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.BugreportManager;
import android.os.Bundle;
import android.os.DropBoxManager;
@@ -81,8 +85,10 @@
import android.util.DisplayMetrics;
import android.view.accessibility.AccessibilityManager;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -96,7 +102,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
@@ -284,6 +290,8 @@
return Context.DROPBOX_SERVICE;
} else if (svcClass == BugreportManager.class) {
return Context.BUGREPORT_SERVICE;
+ } else if (svcClass == TelecomManager.class) {
+ return Context.TELECOM_SERVICE;
}
throw new UnsupportedOperationException(svcClass.getName());
}
@@ -409,12 +417,23 @@
}
@Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) {
+ // Override so that this can be verified via spy.
+ }
+
+ @Override
public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
Bundle options) {
// Override so that this can be verified via spy.
}
@Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
+ int appOp) {
+ // Override so that this can be verified via spy.
+ }
+
+ @Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
int initialCode, String initialData, Bundle initialExtras) {
@@ -617,8 +636,9 @@
private TelecomManager mTelecomManager = mock(TelecomManager.class);
- public ComponentContextFixture() {
+ public ComponentContextFixture(FeatureFlags featureFlags) {
MockitoAnnotations.initMocks(this);
+ when(featureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
when(mResources.getConfiguration()).thenReturn(mResourceConfiguration);
when(mResources.getString(anyInt())).thenReturn("");
when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
@@ -701,7 +721,7 @@
}
}).when(mAppOpsManager).checkPackage(anyInt(), anyString());
- when(mNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+ when(mNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(true);
when(mCarrierConfigManager.getConfig()).thenReturn(new PersistableBundle());
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(new PersistableBundle());
@@ -735,6 +755,14 @@
mServiceInfoByComponentName.put(componentName, serviceInfo);
}
+ public void removeConnectionService(
+ ComponentName componentName,
+ IConnectionService service)
+ throws Exception {
+ removeService(ConnectionService.SERVICE_INTERFACE, componentName, service);
+ mServiceInfoByComponentName.remove(componentName);
+ }
+
public void addInCallService(
ComponentName componentName,
IInCallService service,
@@ -756,6 +784,8 @@
componentName.getPackageName() });
when(mPackageManager.checkPermission(eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mPackageManager.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS),
+ eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED);
when(mPermissionCheckerManager.checkPermission(
eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
any(AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
@@ -794,6 +824,11 @@
when(mResources.getStringArray(eq(id))).thenReturn(value);
}
+ public void putRawResource(int id, String content) {
+ when(mResources.openRawResource(id))
+ .thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
+ }
+
public void setTelecomManager(TelecomManager telecomManager) {
mTelecomManager = telecomManager;
}
@@ -828,6 +863,12 @@
mComponentNameByService.put(service, name);
}
+ private void removeService(String action, ComponentName name, IInterface service) {
+ mComponentNamesByAction.remove(action, name);
+ mServiceByComponentName.remove(name);
+ mComponentNameByService.remove(service);
+ }
+
private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
List<ResolveInfo> result = new ArrayList<>();
for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
index 0d6ceba..ab2c679 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
@@ -16,7 +16,19 @@
package com.android.server.telecom.tests;
-import android.test.suitebuilder.annotation.SmallTest;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
@@ -32,17 +44,6 @@
import org.mockito.Mock;
import org.mockito.Mockito;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-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;
-
@RunWith(JUnit4.class)
public class ConnectionServiceFocusManagerTest extends TelecomTestCase {
diff --git a/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
index 10cac93..7adb32c 100644
--- a/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
@@ -17,11 +17,11 @@
package com.android.server.telecom.tests;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
@@ -32,11 +32,10 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Handler;
import android.os.Looper;
-import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.ContactsAsyncHelper;
@@ -49,7 +48,6 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
-import java.util.concurrent.Executor;
@RunWith(JUnit4.class)
public class ContactsAsyncHelperTest extends TelecomTestCase {
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index 8a85a87..e973992 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -16,10 +16,21 @@
package com.android.server.telecom.tests;
-import android.Manifest;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+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.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
@@ -28,7 +39,8 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.SubscriptionManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallIdMapper;
@@ -38,13 +50,13 @@
import com.android.server.telecom.CreateConnectionProcessor;
import com.android.server.telecom.CreateConnectionResponse;
import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.flags.FeatureFlags;
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.invocation.InvocationOnMock;
@@ -56,20 +68,6 @@
import java.util.Random;
import java.util.UUID;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
/**
* Unit testing for CreateConnectionProcessor as well as CreateConnectionTimeout classes.
*/
@@ -123,7 +121,7 @@
mTestCreateConnectionProcessor = new CreateConnectionProcessor(mMockCall,
mMockConnectionServiceRepository, mMockCreateConnectionResponse,
- mMockAccountRegistrar, mContext);
+ mMockAccountRegistrar, mContext, mFeatureFlags);
mAccountToSub = new HashMap<>();
phoneAccounts = new ArrayList<>();
@@ -842,7 +840,7 @@
ConnectionServiceWrapper wrapper = mock(ConnectionServiceWrapper.class);
when(mMockConnectionServiceRepository.getService(
eq(makeQuickConnectionServiceComponentName()),
- any(UserHandle.class))).thenReturn(wrapper);
+ any(UserHandle.class), any(FeatureFlags.class))).thenReturn(wrapper);
return wrapper;
}
diff --git a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
index e733465..18f2eb0 100644
--- a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
+++ b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
@@ -16,6 +16,14 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -24,7 +32,8 @@
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.RoleManagerAdapter;
@@ -38,14 +47,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class DefaultDialerCacheTest extends TelecomTestCase {
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java
index 2ab4e78..097061b 100644
--- a/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java
@@ -26,11 +26,11 @@
import android.net.Uri;
import android.provider.CallLog;
import android.telecom.CallerInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.callfiltering.CallFilter;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
@@ -44,7 +44,6 @@
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
-
@RunWith(JUnit4.class)
public class DirectToVoicemailFilterTest extends TelecomTestCase {
@Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
diff --git a/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
index 85a5278..5ccfc38 100644
--- a/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
@@ -15,8 +15,15 @@
package com.android.server.telecom.tests;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.media.ToneGenerator;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.DtmfLocalTonePlayer;
@@ -29,12 +36,6 @@
import org.junit.runners.JUnit4;
import org.mockito.Mock;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class DtmfLocalTonePlayerTest extends TelecomTestCase {
private static final int TIMEOUT = 2000;
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
index 3cb8196..c63a3d5 100644
--- a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -35,6 +35,7 @@
import android.net.Uri;
import android.os.BugreportManager;
import android.os.DropBoxManager;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -128,6 +129,7 @@
when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
mTimeouts, mDbm, Runnable::run, mClockProxy);
@@ -171,7 +173,8 @@
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
index 692d720..f2ad2f7 100644
--- a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
@@ -19,12 +19,24 @@
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.telecom.PhoneAccountHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.DefaultDialerCache;
@@ -39,18 +51,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
-
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.any;
-
@RunWith(JUnit4.class)
public class EmergencyCallHelperTest extends TelecomTestCase {
private static final String SYSTEM_DIALER_PACKAGE = "abc.xyz";
diff --git a/tests/src/com/android/server/telecom/tests/EventManagerTest.java b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
index c7d3541..cee0f39 100644
--- a/tests/src/com/android/server/telecom/tests/EventManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
@@ -16,11 +16,17 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import android.net.Uri;
import android.os.Build;
import android.telecom.Log;
import android.telecom.Logging.EventManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
@@ -32,11 +38,6 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
/**
* Unit tests for android.telecom.Logging.EventManager.
*/
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
index 0bfa987..b7e5921 100644
--- a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -16,17 +16,26 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.Intent;
import android.media.session.MediaSession;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.HeadsetMediaButton;
-import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.HeadsetMediaButton.MediaSessionWrapper;
+import com.android.server.telecom.TelecomSystem;
import org.junit.After;
import org.junit.Before;
@@ -37,15 +46,6 @@
import org.mockito.Mock;
import org.mockito.Mockito;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class HeadsetMediaButtonTest extends TelecomTestCase {
private static final int TEST_TIMEOUT_MILLIS = 1000;
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 683a5e2..bd0e97f 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -28,15 +28,13 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -53,6 +51,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.UiModeManager;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.AttributionSource;
import android.content.AttributionSourceState;
import android.content.BroadcastReceiver;
@@ -70,7 +69,6 @@
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.compat.testing.PlatformCompatChangeRule;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -87,10 +85,11 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.telecom.IInCallAdapter;
import com.android.internal.telecom.IInCallService;
@@ -143,6 +142,8 @@
@Mock PackageManager mMockPackageManager;
@Mock PermissionCheckerManager mMockPermissionCheckerManager;
@Mock Call mMockCall;
+ @Mock Call mMockSystemCall1;
+ @Mock Call mMockSystemCall2;
@Mock Resources mMockResources;
@Mock AppOpsManager mMockAppOpsManager;
@Mock MockContext mMockContext;
@@ -205,6 +206,7 @@
private UserHandle mChildUserHandle = UserHandle.of(10);
private @Mock Call mMockChildUserCall;
private UserHandle mParentUserHandle = UserHandle.of(1);
+ private @Mock com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@Override
@Before
@@ -234,9 +236,11 @@
mMockPermissionInfo);
when(mMockContext.getAttributionSource()).thenReturn(new AttributionSource(Process.myUid(),
"com.android.server.telecom.tests", null));
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
- mEmergencyCallHelper, mCarModeTracker, mClockProxy);
+ mEmergencyCallHelper, mCarModeTracker, mClockProxy, mFeatureFlags,
+ mTelephonyFeatureFlags);
// Capture the broadcast receiver registered.
doAnswer(invocation -> {
mRegisteredReceiver = invocation.getArgument(0);
@@ -588,6 +592,7 @@
when(mMockCall.isEmergencyCall()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockContext.getSystemService(eq(UserManager.class)))
.thenReturn(mMockUserManager);
when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
@@ -617,6 +622,7 @@
when(mMockCall.isInECBM()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockContext.getSystemService(eq(UserManager.class)))
.thenReturn(mMockUserManager);
when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
@@ -647,6 +653,7 @@
when(mMockCall.isInECBM()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockContext.getSystemService(eq(UserManager.class)))
.thenReturn(mMockUserManager);
when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
@@ -1839,7 +1846,68 @@
assertNull(mInCallController.getInCallServiceConnections().get(testUser));
}
- private void setupMocksForWorkProfileTest() {
+ @Test
+ public void testRemoveAllServiceConnections_MultiUser() throws Exception {
+ when(mFeatureFlags.associatedUserRefactorForWorkProfile()).thenReturn(true);
+ setupMocks(false /* isExternalCall */);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ UserHandle workUser = new UserHandle(12);
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
+ when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+ when(mMockCall.getAssociatedUser()).thenReturn(workUser);
+ setupFakeSystemCall(mMockSystemCall1, 1);
+ setupFakeSystemCall(mMockSystemCall2, 2);
+
+ // Add "work" call to service. The mapping should've been inserted
+ // with the workUser as the key.
+ mInCallController.onCallAdded(mMockCall);
+ // Add system call to service. The mapping should've been
+ // inserted with the system user as the key.
+ mInCallController.onCallAdded(mMockSystemCall1);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ // Make sure we bound to the system call as well as the work call.
+ verify(mMockContext, times(2)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(UserHandle.CURRENT));
+ assertTrue(mInCallController.getInCallServiceConnections().containsKey(workUser));
+ assertTrue(mInCallController.getInCallServiceConnections().containsKey(UserHandle.SYSTEM));
+
+ // Remove the work call. This leverages getUserFromCall to remove the ICS mapping.
+ when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockSystemCall1));
+ mInCallController.onCallRemoved(mMockCall);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ // Verify that the mapping was properly removed.
+ assertNull(mInCallController.getInCallServiceConnections().get(workUser));
+ // Verify mapping for system user is still present.
+ assertNotNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+
+ // Add another system call
+ mInCallController.onCallAdded(mMockSystemCall2);
+ when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockSystemCall2));
+ // Remove first system call and verify that mapping is present
+ mInCallController.onCallRemoved(mMockSystemCall1);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ // Verify mapping for system user is still present.
+ assertNotNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+ // Remove last system call and verify that connection isn't present in ICS mapping.
+ when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+ mInCallController.onCallRemoved(mMockSystemCall2);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ assertNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+ }
+
+ private void setupFakeSystemCall(@Mock Call call, int id) {
+ when(call.getAssociatedUser()).thenReturn(UserHandle.SYSTEM);
+ when(call.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(call.getAnalytics()).thenReturn(new Analytics.CallInfo());
+ when(call.getId()).thenReturn("TC@" + id);
+ }
+
+ private void setupMocksForProfileTest() {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockChildUserCall.isIncoming()).thenReturn(false);
@@ -1855,9 +1923,9 @@
when(mMockUserInfo.getUserHandle()).thenReturn(mParentUserHandle);
when(mMockChildUserInfo.getUserHandle()).thenReturn(mChildUserHandle);
when(mMockUserInfo.isManagedProfile()).thenReturn(false);
- when(mMockChildUserInfo.isManagedProfile()).thenReturn(true);
+ when(mMockChildUserInfo.isManagedProfile()).thenReturn(false);
when(mMockChildUserCall.getAssociatedUser()).thenReturn(mChildUserHandle);
- when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mChildUserHandle);
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mParentUserHandle);
when(mMockUserManager.getProfileParent(mChildUserHandle.getIdentifier())).thenReturn(
mMockUserInfo);
when(mMockUserManager.getProfileParent(mChildUserHandle)).thenReturn(mParentUserHandle);
@@ -1865,14 +1933,14 @@
mMockUserInfo);
when(mMockUserManager.getUserInfo(eq(mChildUserHandle.getIdentifier()))).thenReturn(
mMockChildUserInfo);
- when(mMockUserManager.isManagedProfile(mChildUserHandle.getIdentifier())).thenReturn(true);
when(mMockUserManager.isManagedProfile(mParentUserHandle.getIdentifier())).thenReturn(
false);
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);
}
@Test
- public void testManagedProfileCallQueriesIcsUsingParentUserToo() throws Exception {
- setupMocksForWorkProfileTest();
+ public void testProfileCallQueriesIcsUsingParentUserToo() throws Exception {
+ setupMocksForProfileTest();
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManager(true /* default */,
true /*useNonUiInCalls*/, true /*useAppOpNonUiInCalls*/,
@@ -1881,7 +1949,7 @@
true /*includeSelfManagedCallsInCarModeDialer*/,
true /*includeSelfManagedCallsInNonUi*/);
- //pass in call by child/work-profileuser
+ //pass in call by child/profile user
mInCallController.bindToServices(mMockChildUserCall);
// Verify that queryIntentServicesAsUser is also called with parent handle
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 88b5bb5..39381e6 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IInCallAdapter;
import com.android.internal.telecom.IInCallService;
@@ -39,7 +40,7 @@
* Controls a test {@link IInCallService} as would be provided by an InCall UI on a system.
*/
public class InCallServiceFixture implements TestFixture<IInCallService> {
-
+ public static boolean sIgnoreOverrideAdapterFlag = false;
public String mLatestCallId;
public IInCallAdapter mInCallAdapter;
public CallAudioState mCallAudioState;
@@ -53,10 +54,17 @@
public CountDownLatch mUpdateCallLock = new CountDownLatch(1);
public CountDownLatch mAddCallLock = new CountDownLatch(1);
+ @VisibleForTesting
+ public static void setIgnoreOverrideAdapterFlag(boolean flag) {
+ sIgnoreOverrideAdapterFlag = flag;
+ }
+
public class FakeInCallService extends IInCallService.Stub {
@Override
public void setInCallAdapter(IInCallAdapter inCallAdapter) throws RemoteException {
- if (mInCallAdapter != null && inCallAdapter != null) {
+ // sIgnoreOverrideAdapterFlag is being used to verify a scenario where the InCallAdapter
+ // gets set twice (secondary user places MO/MT call).
+ if (mInCallAdapter != null && inCallAdapter != null && !sIgnoreOverrideAdapterFlag) {
throw new RuntimeException("Adapter is already set");
}
if (mInCallAdapter == null && inCallAdapter == null) {
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index f11afc1..c9faa52 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -23,7 +23,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,8 +32,11 @@
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.ToneGenerator;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -69,6 +71,7 @@
@Mock private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
@Mock private WiredHeadsetManager mWiredHeadsetManager;
@Mock private DockManager mDockManager;
+ @Mock private AsyncRingtonePlayer mRingtonePlayer;
@Mock private BluetoothDevice mDevice;
@Mock private BluetoothAdapter mBluetoothAdapter;
@@ -110,6 +113,8 @@
@Mock
private CallAudioManager mCallAudioManager;
+ @Mock
+ private Call mCall;
private InCallTonePlayer mInCallTonePlayer;
@@ -120,15 +125,15 @@
when(mToneGeneratorFactory.get(anyInt(), anyInt())).thenReturn(mToneGenerator);
when(mMediaPlayerFactory.get(anyInt(), any())).thenReturn(mMediaPlayerAdapter);
- doNothing().when(mCallAudioManager).setIsTonePlaying(anyBoolean());
+ doNothing().when(mCallAudioManager).setIsTonePlaying(any(Call.class), anyBoolean());
mCallAudioRoutePeripheralAdapter = new CallAudioRoutePeripheralAdapter(
mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager,
- mDockManager);
+ mDockManager, mRingtonePlayer);
mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
mFactory.setCallAudioManager(mCallAudioManager);
- mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+ mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_ENDED);
}
@Override
@@ -145,11 +150,12 @@
assertTrue(mInCallTonePlayer.startTone());
// Verify we did play a tone.
verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
mInCallTonePlayer.stopTone();
// Timeouts due to threads!
- verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+ eq(false));
}
@SmallTest
@@ -159,11 +165,12 @@
assertTrue(mInCallTonePlayer.startTone());
// Verify we did play a tone.
verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
mInCallTonePlayer.stopTone();
// Timeouts due to threads!
- verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+ eq(false));
// Correctness check: ensure we can't start the tone again.
assertFalse(mInCallTonePlayer.startTone());
@@ -172,15 +179,16 @@
@SmallTest
@Test
public void testInterruptToneGenerator() {
- mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+ mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
assertTrue(mInCallTonePlayer.startTone());
verify(mToneGenerator, timeout(TEST_TIMEOUT)).startTone(anyInt());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
mInCallTonePlayer.stopTone();
// Timeouts due to threads!
- verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+ eq(false));
// Ideally it would be nice to verify this, however release is a native method so appears to
// cause flakiness when testing on Cuttlefish.
// verify(mToneGenerator, timeout(TEST_TIMEOUT)).release();
@@ -197,7 +205,8 @@
// Verify we did play a tone.
verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
- verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+ eq(true));
}
@SmallTest
@@ -211,11 +220,11 @@
when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
- mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+ mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
assertTrue(mInCallTonePlayer.startTone());
verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
.get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
}
@SmallTest
@@ -229,11 +238,11 @@
when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
- mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
assertTrue(mInCallTonePlayer.startTone());
verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
.get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
}
@SmallTest
@@ -247,11 +256,11 @@
when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
- mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+ mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
assertTrue(mInCallTonePlayer.startTone());
verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
.get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
}
@SmallTest
@@ -265,10 +274,10 @@
when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
- mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
assertTrue(mInCallTonePlayer.startTone());
verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
.get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
- verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
}
}
diff --git a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
index f935908..cdf2542 100644
--- a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
@@ -17,13 +17,14 @@
package com.android.server.telecom.tests;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.never;
import android.os.PowerManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
index 9269836..66ac553 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
@@ -16,11 +16,16 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.when;
+
import android.content.ContentResolver;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.TelecomSystem;
@@ -30,15 +35,11 @@
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.when;
-
import org.junit.Before;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
-import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
index 914fdc5..2d81bb3 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -16,15 +16,21 @@
package com.android.server.telecom.tests;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.app.NotificationManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.UserHandle;
-import android.telecom.PhoneAccountHandle;
import android.telecom.VideoProfile;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
@@ -38,14 +44,6 @@
import org.junit.runners.JUnit4;
import org.mockito.Mock;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
/**
* Tests for the {@link com.android.server.telecom.ui.IncomingCallNotifier} class.
*/
diff --git a/tests/src/com/android/server/telecom/tests/LogUtilsTest.java b/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
index 637dfbc..4393d90 100644
--- a/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertTrue;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.LogUtils;
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index 2b05430..61e8347 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -16,9 +16,30 @@
package com.android.server.telecom.tests;
+import static com.android.server.telecom.ui.MissedCallNotifierImpl.CALL_LOG_COLUMN_ID;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
@@ -29,8 +50,6 @@
import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -39,13 +58,14 @@
import android.os.Looper;
import android.os.UserHandle;
import android.provider.CallLog;
+import android.telecom.CallerInfo;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.telecom.CallerInfo;
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.Constants;
import com.android.server.telecom.DefaultDialerCache;
@@ -67,32 +87,11 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class MissedCallNotifierImplTest extends TelecomTestCase {
@@ -241,7 +240,7 @@
MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
CALL_TIMESTAMP, phoneAccount.getAccountHandle());
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(),
anyString(), any());
@@ -250,6 +249,31 @@
assertEquals(1, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1));
}
+ @SmallTest
+ @Test
+ public void testCallLogUriSentToNotifier(){
+ MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer();
+ PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+ Cursor mockMissedCallsCursor = new MockMissedCallCursorBuilder()
+ .addEntry(TEL_CALL_HANDLE.getSchemeSpecificPart(),
+ CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP)
+ .build();
+ MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
+ CALL_TIMESTAMP, phoneAccount.getAccountHandle());
+ when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(true);
+
+ missedCallNotifier.showMissedCallNotification(fakeCall,
+ CallLog.Calls.CONTENT_URI.buildUpon().appendPath(Long.toString(
+ mockMissedCallsCursor.getInt(CALL_LOG_COLUMN_ID))).build());
+ ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(),
+ anyString(), any());
+
+ Intent sentIntent = intentArgumentCaptor.getValue();
+ Uri actualCallUri = sentIntent.getParcelableExtra(TelecomManager.EXTRA_CALL_LOG_URI);
+ assertTrue(actualCallUri.isPathPrefixMatch(CallLog.Calls.CONTENT_URI));
+ }
+
private MissedCallNotifier setupMissedCallNotificationThroughDefaultDialer() {
mComponentContextFixture.addIntentReceiver(
TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, COMPONENT_NAME);
@@ -275,9 +299,9 @@
MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
CALL_TIMESTAMP, phoneAccount.getAccountHandle());
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */null);
missedCallNotifier.clearMissedCalls(userHandle);
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */null);
ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(
Integer.class);
@@ -308,10 +332,10 @@
MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
- mDeviceIdleControllerAdapter);
+ mDeviceIdleControllerAdapter, mFeatureFlags);
- missedCallNotifier.showMissedCallNotification(fakeCall);
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
// The following captor is to capture the two notifications that got passed into
// notifyAsUser. This distinguishes between the builders used for the full notification
@@ -402,7 +426,7 @@
MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
CALL_TIMESTAMP, phoneAccount.getAccountHandle());
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
Notification.class);
@@ -464,13 +488,13 @@
MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
- mDeviceIdleControllerAdapter);
+ mDeviceIdleControllerAdapter, mFeatureFlags);
PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
MissedCallNotifier.CallInfo fakeCall =
makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
phoneAccount.getAccountHandle());
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
// Create two intents that correspond to call-back and respond back with SMS, and assert
// that in the case of a SIP call, no SMS intent is generated.
@@ -525,7 +549,7 @@
MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
- mDeviceIdleControllerAdapter);
+ mDeviceIdleControllerAdapter, mFeatureFlags);
// AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below
// timeout-verify, so run this in a new handler to mitigate that.
@@ -595,7 +619,7 @@
MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
- mDeviceIdleControllerAdapter);
+ mDeviceIdleControllerAdapter, mFeatureFlags);
// AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below
// timeout-verify, so run this in a new handler to mitigate that.
@@ -637,13 +661,13 @@
MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
- mDeviceIdleControllerAdapter);
+ mDeviceIdleControllerAdapter, mFeatureFlags);
PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
MissedCallNotifier.CallInfo fakeCall =
makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
phoneAccount.getAccountHandle());
- missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<Bundle> bundleCaptor =
@@ -701,7 +725,7 @@
NotificationBuilderFactory fakeBuilderFactory, UserHandle currentUser) {
MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
- mDeviceIdleControllerAdapter);
+ mDeviceIdleControllerAdapter, mFeatureFlags);
missedCallNotifier.setCurrentUserHandle(currentUser);
return missedCallNotifier;
}
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java
index e441835..c0e3435 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java
@@ -16,14 +16,17 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
import android.content.ComponentName;
import android.net.Uri;
-import android.telecom.PhoneAccountHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.telecom.CallerInfo;
+import android.telecom.PhoneAccountHandle;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.MissedCallNotifier;
-import com.android.server.telecom.MissedCallNotifier.CallInfo;
import org.junit.After;
import org.junit.Before;
@@ -31,9 +34,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
@RunWith(JUnit4.class)
public class MissedCallNotifierTest extends TelecomTestCase {
private static final ComponentName COMPONENT_NAME =
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index 4af3de3..0c3588e 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -32,9 +32,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -109,6 +109,7 @@
mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
mNotificationManager = spy((NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE));
+ when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
when(mContentProvider.call(any(String.class), any(String.class),
@@ -152,6 +153,8 @@
setUpEmergencyCall();
when(mEmergencyCall.getAssociatedUser()).
thenReturn(mPhoneAccountA0.getAccountHandle().getUserHandle());
+ when(mEmergencyCall.getTargetPhoneAccount())
+ .thenReturn(mPhoneAccountA0.getAccountHandle());
mCallsManager.addCall(mEmergencyCall);
assertTrue(mCallsManager.isInEmergencyCall());
@@ -417,7 +420,7 @@
null, mCallsManager.getPhoneNumberUtilsAdapter(), null,
null, null, mPhoneAccountA0.getAccountHandle(),
Call.CALL_DIRECTION_INCOMING, false, false,
- mClockProxy, null));
+ mClockProxy, null, mFeatureFlags));
doReturn(1L).when(mIncomingCall).getStartRingTime();
doAnswer((x) -> {
mCountDownLatch.countDown();
diff --git a/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
index ed74637..3f4b5f6 100644
--- a/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
@@ -20,7 +20,8 @@
import static org.junit.Assert.assertTrue;
import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.MmiUtils;
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 33acd98..e75ad97 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -20,17 +20,16 @@
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.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNotNull;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,7 +50,8 @@
import android.telecom.VideoProfile;
import android.telephony.DisconnectCause;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
@@ -64,6 +64,7 @@
import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
@@ -75,6 +76,8 @@
@RunWith(JUnit4.class)
public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase {
+ private static final Uri TEST_URI = Uri.parse("tel:16505551212");
+
private static class ReceiverIntentPair {
public BroadcastReceiver receiver;
public Intent intent;
@@ -93,6 +96,7 @@
@Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock private RoleManagerAdapter mRoleManagerAdapter;
@Mock private DefaultDialerCache mDefaultDialerCache;
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private MmiUtils mMmiUtils;
private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new PhoneNumberUtilsAdapterImpl();
@@ -113,6 +117,7 @@
any(PhoneAccountHandle.class))).thenReturn(mPhoneAccount);
when(mPhoneAccount.isSelfManaged()).thenReturn(true);
when(mSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(false);
+ when(mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()).thenReturn(false);
}
@Override
@@ -510,6 +515,84 @@
testUnmodifiedRegularCall();
}
+ /**
+ * Where the flag `isNewOutgoingCallBroadcastUnblocking` is off, verify that we sent an ordered
+ * broadcast and did not try to start the call immediately (legacy behavior).
+ */
+ @SmallTest
+ @Test
+ public void testSendBroadcastBlocking() {
+ when(mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()).thenReturn(false);
+ Intent intent = new Intent(Intent.ACTION_CALL, TEST_URI);
+ NewOutgoingCallIntentBroadcaster nocib = new NewOutgoingCallIntentBroadcaster(
+ mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
+ true /* isDefaultPhoneApp */, mDefaultDialerCache, mMmiUtils, mFeatureFlags);
+
+ NewOutgoingCallIntentBroadcaster.CallDisposition disposition = nocib.evaluateCall();
+ nocib.processCall(mCall, disposition);
+
+ // We should not have not short-circuited to place the outgoing call directly.
+ verify(mCall, never()).setNewOutgoingCallIntentBroadcastIsDone();
+ verify(mCallsManager, never()).placeOutgoingCall(any(Call.class), any(Uri.class),
+ any(GatewayInfo.class), anyBoolean(), anyInt());
+
+ // Ensure we did send the broadcast ordered
+ verifyBroadcastSent(TEST_URI.getSchemeSpecificPart(),
+ createNumberExtras(TEST_URI.getSchemeSpecificPart()));
+
+ // Ensure we did not try to directly send the broadcast unordered.
+ verify(mContext, never()).sendBroadcastAsUser(
+ any(Intent.class),
+ eq(UserHandle.CURRENT),
+ eq(android.Manifest.permission.PROCESS_OUTGOING_CALLS));
+ }
+
+ /**
+ * Where the flag `isNewOutgoingCallBroadcastUnblocking` is off, verify that we sent an ordered
+ * broadcast and did not try to start the call immediately. Also ensure that the broadcast
+ * flags are correct.
+ */
+ @SmallTest
+ @Test
+ public void testSendBroadcastNonBlocking() {
+ when(mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()).thenReturn(true);
+ Intent intent = new Intent(Intent.ACTION_CALL, TEST_URI);
+ NewOutgoingCallIntentBroadcaster nocib = new NewOutgoingCallIntentBroadcaster(
+ mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
+ true /* isDefaultPhoneApp */, mDefaultDialerCache, mMmiUtils, mFeatureFlags);
+
+ NewOutgoingCallIntentBroadcaster.CallDisposition disposition = nocib.evaluateCall();
+ nocib.processCall(mCall, disposition);
+
+ // We should have started the outgoing call flow immediately.
+ verify(mCall).setNewOutgoingCallIntentBroadcastIsDone();
+ verify(mCallsManager).placeOutgoingCall(any(Call.class), any(Uri.class),
+ nullable(GatewayInfo.class), anyBoolean(), anyInt());
+
+ // Ensure we didn't send an ordered broadcast.
+ verify(mContext, never()).sendOrderedBroadcastAsUser(
+ any(Intent.class),
+ any(UserHandle.class),
+ anyString(),
+ anyInt(),
+ any(Bundle.class),
+ any(BroadcastReceiver.class),
+ any(Handler.class),
+ eq(Activity.RESULT_OK),
+ anyString(),
+ any(Bundle.class));
+
+ // But that we did send a regular broadcast.
+ ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(
+ intentArgumentCaptor.capture(),
+ eq(UserHandle.CURRENT),
+ eq(android.Manifest.permission.PROCESS_OUTGOING_CALLS),
+ eq(AppOpsManager.OP_PROCESS_OUTGOING_CALLS));
+ Intent capturedIntent = intentArgumentCaptor.getValue();
+ assertEquals(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, capturedIntent.getFlags());
+ }
+
private ReceiverIntentPair regularCallTestHelper(Intent intent,
Bundle expectedAdditionalExtras) {
Uri handle = intent.getData();
@@ -542,7 +625,7 @@
boolean isDefaultPhoneApp) {
NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
- isDefaultPhoneApp, mDefaultDialerCache, mMmiUtils);
+ isDefaultPhoneApp, mDefaultDialerCache, mMmiUtils, mFeatureFlags);
NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
b.processCall(mCall, cd);
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index fed8084..8eefd96 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -13,11 +13,13 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.telecom.Connection;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
import android.telephony.ims.ImsCallProfile;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallerInfoLookupHelper;
@@ -57,6 +59,7 @@
when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any())).thenReturn(null);
when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
.thenReturn(false);
@@ -75,7 +78,8 @@
false /* shouldAttachToExistingConnection */,
false /* isConference */,
mClockProxy /* ClockProxy */,
- mToastProxy);
+ mToastProxy,
+ mFeatureFlags);
}
@Override
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index e573bb8..2d10f40 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -23,14 +23,12 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,11 +55,11 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.MediumTest;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.util.FastXmlSerializer;
@@ -117,6 +115,7 @@
@Mock private TelecomManager mTelecomManager;
@Mock private DefaultDialerCache mDefaultDialerCache;
@Mock private AppLabelProxy mAppLabelProxy;
+ @Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@Override
@Before
@@ -134,8 +133,10 @@
when(mAppLabelProxy.getAppLabel(anyString()))
.thenReturn(TEST_LABEL);
mRegistrar = new PhoneAccountRegistrar(
- mComponentContextFixture.getTestDouble().getApplicationContext(),
- mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
+ mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, FILE_NAME,
+ mDefaultDialerCache, mAppLabelProxy, mTelephonyFeatureFlags);
+ when(mFeatureFlags.onlyUpdateTelephonyOnValidSubIds()).thenReturn(false);
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
}
@Override
@@ -1700,6 +1701,56 @@
}
}
+ @Test
+ public void testGetPhoneAccountAcrossUsers() throws Exception {
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ PhoneAccount accountForCurrent = makeQuickAccountBuilder("id_0", 0, UserHandle.CURRENT)
+ .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
+ PhoneAccount accountForAll = makeQuickAccountBuilder("id_0", 0, UserHandle.ALL)
+ .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_MULTI_USER).build();
+ PhoneAccount accountForWorkProfile = makeQuickAccountBuilder("id_1", 1, USER_HANDLE_10)
+ .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
+
+ registerAndEnableAccount(accountForCurrent);
+ registerAndEnableAccount(accountForAll);
+ registerAndEnableAccount(accountForWorkProfile);
+
+ List<PhoneAccount> accountsForUser = mRegistrar.getPhoneAccounts(0, 0,
+ null, null, false, USER_HANDLE_10, false, false);
+ List<PhoneAccount> accountsVisibleUser = mRegistrar.getPhoneAccounts(0, 0,
+ null, null, false, USER_HANDLE_10, false, true);
+ List<PhoneAccount> accountsAcrossUser = mRegistrar.getPhoneAccounts(0, 0,
+ null, null, false, USER_HANDLE_10, true, false);
+
+ // Return the account exactly matching the user if it exists
+ assertEquals(1, accountsForUser.size());
+ assertTrue(accountsForUser.contains(accountForWorkProfile));
+ // The accounts visible to the user without across user permission
+ assertEquals(2, accountsVisibleUser.size());
+ assertTrue(accountsVisibleUser.containsAll(accountsForUser));
+ assertTrue(accountsVisibleUser.contains(accountForAll));
+ // The accounts visible to the user with across user permission
+ assertEquals(3, accountsAcrossUser.size());
+ assertTrue(accountsAcrossUser.containsAll(accountsVisibleUser));
+ assertTrue(accountsAcrossUser.contains(accountForCurrent));
+
+ mRegistrar.unregisterPhoneAccount(accountForWorkProfile.getAccountHandle());
+
+ accountsForUser = mRegistrar.getPhoneAccounts(0, 0,
+ null, null, false, USER_HANDLE_10, false, false);
+
+ // Return the account visible for the user if no account exactly matches the user
+ assertEquals(1, accountsForUser.size());
+ assertTrue(accountsForUser.contains(accountForAll));
+ }
+
private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
return new PhoneAccount.Builder(handle, TEST_LABEL)
.setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
diff --git a/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java b/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
index 807b7cf..310f4cb 100644
--- a/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
@@ -16,8 +16,14 @@
package com.android.server.telecom.tests;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.os.PowerManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
@@ -33,11 +39,6 @@
import java.util.List;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class ProximitySensorManagerTest extends TelecomTestCase{
diff --git a/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java b/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java
new file mode 100644
index 0000000..e8d39c8
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.RingbackPlayer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(JUnit4.class)
+public class RingbackPlayerTest extends TelecomTestCase {
+ @Mock InCallTonePlayer.Factory mFactory;
+ @Mock Call mCall;
+ @Mock InCallTonePlayer mTonePlayer;
+
+ private RingbackPlayer mRingbackPlayer;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mFactory.createPlayer(any(Call.class), anyInt())).thenReturn(mTonePlayer);
+ mRingbackPlayer = new RingbackPlayer(mFactory);
+ }
+
+ @SmallTest
+ @Test
+ public void testPlayerSync() {
+ // make sure InCallTonePlayer try to start playing the tone after RingbackPlayer receives
+ // stop tone request.
+ CountDownLatch latch = new CountDownLatch(1);
+ doReturn(CallState.DIALING).when(mCall).getState();
+ doAnswer(x -> {
+ new Thread(() -> {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }).start();
+ return true;
+ }).when(mTonePlayer).startTone();
+
+ mRingbackPlayer.startRingbackForCall(mCall);
+ mRingbackPlayer.stopRingbackForCall(mCall);
+ assertFalse(mRingbackPlayer.isRingbackPlaying());
+ latch.countDown();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index a4adf77..a0ec267 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -16,8 +16,10 @@
package com.android.server.telecom.tests;
+import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -30,6 +32,8 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -50,9 +54,13 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.VibratorInfo;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
@@ -62,33 +70,47 @@
import com.android.server.telecom.Ringer;
import com.android.server.telecom.RingtoneFactory;
import com.android.server.telecom.SystemSettingsUtil;
+import com.android.server.telecom.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.Spy;
+import java.time.Duration;
import java.util.concurrent.CompletableFuture;
@RunWith(JUnit4.class)
public class RingerTest extends TelecomTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
// Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
// device configuration for ringtone URIs. The actual Uri can be verified via the
// VibrationEffectProxy mock invocation.
private static final VibrationEffect URI_VIBRATION_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+ private static final VibrationEffect EXPECTED_SIMPLE_VIBRATION_PATTERN =
+ VibrationEffect.createWaveform(
+ new long[] {0, 1000, 1000}, new int[] {0, 255, 0}, 1);
+ private static final VibrationEffect EXPECTED_PULSE_VIBRATION_PATTERN =
+ VibrationEffect.createWaveform(
+ Ringer.PULSE_PATTERN, Ringer.PULSE_AMPLITUDE, 5);
@Mock InCallTonePlayer.Factory mockPlayerFactory;
@Mock SystemSettingsUtil mockSystemSettingsUtil;
@Mock RingtoneFactory mockRingtoneFactory;
@Mock Vibrator mockVibrator;
+ @Mock VibratorInfo mockVibratorInfo;
@Mock InCallController mockInCallController;
@Mock NotificationManager mockNotificationManager;
@Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
+ @Mock private FeatureFlags mFeatureFlags;
@Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
@@ -110,21 +132,28 @@
@Before
public void setUp() throws Exception {
super.setUp();
- mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mContext = spy(mComponentContextFixture.getTestDouble().getApplicationContext());
+ when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
doReturn(URI_VIBRATION_EFFECT).when(spyVibrationEffectProxy).get(any(), any());
- when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
+ when(mockPlayerFactory.createPlayer(any(Call.class), anyInt())).thenReturn(mockTonePlayer);
mockAudioManager = mContext.getSystemService(AudioManager.class);
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mockVibrator.getInfo()).thenReturn(mockVibratorInfo);
when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
.thenAnswer((invocation) -> mIsHapticPlaybackSupported);
mockNotificationManager =mContext.getSystemService(NotificationManager.class);
when(mockTonePlayer.startTone()).thenReturn(true);
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+ when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(true);
when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
when(mockCall1.getState()).thenReturn(CallState.RINGING);
when(mockCall2.getState()).thenReturn(CallState.RINGING);
when(mockCall1.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
when(mockCall2.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
+ when(mockCall1.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(mockCall2.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ // Set BT active state in tests to ensure that we do not end up blocking tests for 1 sec
+ // waiting for BT to connect in unit tests by default.
+ asyncRingtonePlayer.updateBtActiveState(true);
createRingerUnderTest();
}
@@ -136,7 +165,8 @@
private void createRingerUnderTest() {
mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
- mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
+ mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter,
+ mFeatureFlags);
// This future is used to wait for AsyncRingtonePlayer to finish its part.
mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
}
@@ -149,6 +179,151 @@
@SmallTest
@Test
+ public void testSimpleVibrationPrecedesValidSupportedDefaultRingVibrationOverride()
+ throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration-effect>
+ <predefined-effect name="click"/>
+ </vibration-effect>
+ """,
+ /* useSimpleVibration= */ true);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testDefaultRingVibrationOverrideNotUsedWhenFeatureIsDisabled()
+ throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(false);
+ mockVibrationResourceValues(
+ """
+ <vibration-effect>
+ <waveform-effect>
+ <waveform-entry durationMs="100" amplitude="0"/>
+ <repeating>
+ <waveform-entry durationMs="500" amplitude="default"/>
+ <waveform-entry durationMs="700" amplitude="0"/>
+ </repeating>
+ </waveform-effect>
+ </vibration-effect>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_PULSE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testValidSupportedRepeatingDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration-effect>
+ <waveform-effect>
+ <waveform-entry durationMs="100" amplitude="0"/>
+ <repeating>
+ <waveform-entry durationMs="500" amplitude="default"/>
+ <waveform-entry durationMs="700" amplitude="0"/>
+ </repeating>
+ </waveform-effect>
+ </vibration-effect>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(
+ VibrationEffect.createWaveform(new long[]{100, 500, 700}, /* repeat= */ 1),
+ mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testValidSupportedNonRepeatingDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration-effect>
+ <predefined-effect name="click"/>
+ </vibration-effect>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(
+ VibrationEffect
+ .startComposition()
+ .repeatEffectIndefinitely(
+ VibrationEffect
+ .startComposition()
+ .addEffect(VibrationEffect.createPredefined(EFFECT_CLICK))
+ .addOffDuration(Duration.ofSeconds(1))
+ .compose()
+ )
+ .compose(),
+ mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testValidButUnsupportedDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration-effect>
+ <predefined-effect name="click"/>
+ </vibration-effect>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(
+ eq(VibrationEffect.createPredefined(EFFECT_CLICK)))).thenReturn(false);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testInvalidDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ /* defaultVibrationContent= */ "bad serialization",
+ /* useSimpleVibration= */ false);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testEmptyDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ /* defaultVibrationContent= */ "", /* useSimpleVibration= */ false);
+ when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
public void testNoActionInTheaterMode() throws Exception {
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
@@ -227,7 +402,7 @@
@SmallTest
@Test
public void testCallWaitingButNoRingForSpecificContacts() throws Exception {
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+ when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(false);
// Start call waiting to make sure that it does stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
verify(mockTonePlayer).startTone();
@@ -434,6 +609,48 @@
verify(mockRingtone).play();
}
+ @SmallTest
+ @Test
+ public void testDelayRingerForBtHfpDevices() throws Exception {
+ asyncRingtonePlayer.updateBtActiveState(false);
+ Ringtone mockRingtone = ensureRingtoneMocked();
+
+ ensureRingerIsAudible();
+ assertTrue(mRingerUnderTest.startRinging(mockCall1, true));
+ assertTrue(mRingerUnderTest.isRinging());
+ // We should not have the ringtone play until BT moves active
+ verify(mockRingtone, never()).play();
+
+ asyncRingtonePlayer.updateBtActiveState(true);
+ mRingCompletionFuture.get();
+ verify(mockRingtoneFactory, times(1))
+ .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class),
+ anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
+ verify(mockRingtone).play();
+
+ mRingerUnderTest.stopRinging();
+ verify(mockRingtone, timeout(1000/*ms*/)).stop();
+ assertFalse(mRingerUnderTest.isRinging());
+ }
+
+ @SmallTest
+ @Test
+ public void testUnblockRingerForStopCommand() throws Exception {
+ asyncRingtonePlayer.updateBtActiveState(false);
+ Ringtone mockRingtone = ensureRingtoneMocked();
+
+ ensureRingerIsAudible();
+ assertTrue(mRingerUnderTest.startRinging(mockCall1, true));
+ // We should not have the ringtone play until BT moves active
+ verify(mockRingtone, never()).play();
+
+ // We are not setting BT active, but calling stop ringing while the other thread is waiting
+ // for BT active should also unblock it.
+ mRingerUnderTest.stopRinging();
+ verify(mockRingtone, timeout(1000/*ms*/)).stop();
+ }
+
/**
* test shouldRingForContact will suppress the incoming call if matchesCallFilter returns
* false (meaning DND is ON and the caller cannot bypass the settings)
@@ -446,7 +663,7 @@
when(mContext.getSystemService(NotificationManager.class)).thenReturn(
mockNotificationManager);
// suppress the call
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+ when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(false);
// run the method under test
assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
@@ -455,7 +672,7 @@
// verify we never set the call object and matchesCallFilter is called
verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(true);
verify(mockNotificationManager, times(1))
- .matchesCallFilter(any(Bundle.class));
+ .matchesCallFilter(any(Uri.class));
}
/**
@@ -468,7 +685,6 @@
when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
// alert the user of the call
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
// run the method under test
assertTrue(mRingerUnderTest.shouldRingForContact(mockCall1));
@@ -477,7 +693,7 @@
// verify we never set the call object and matchesCallFilter is called
verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(false);
verify(mockNotificationManager, times(1))
- .matchesCallFilter(any(Bundle.class));
+ .matchesCallFilter(any(Uri.class));
}
/**
@@ -493,7 +709,7 @@
// THEN
assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(false);
- verify(mockNotificationManager, never()).matchesCallFilter(any(Bundle.class));
+ verify(mockNotificationManager, never()).matchesCallFilter(any(Uri.class));
}
@Test
@@ -503,7 +719,7 @@
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+ when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(false);
assertFalse(mRingerUnderTest.shouldRingForContact(mockCall2));
assertFalse(startRingingAndWaitForAsync(mockCall2, false));
@@ -518,7 +734,6 @@
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
assertTrue(startRingingAndWaitForAsync(mockCall2, false));
@@ -545,7 +760,6 @@
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
- when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
@@ -624,4 +838,13 @@
when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
return mockRingtone;
}
+
+ private void mockVibrationResourceValues(
+ String defaultVibrationContent, boolean useSimpleVibration) {
+ mComponentContextFixture.putRawResource(
+ com.android.internal.R.raw.default_ringtone_vibration_effect,
+ defaultVibrationContent);
+ mComponentContextFixture.putBooleanResource(
+ R.bool.use_simple_vibration_pattern, useSimpleVibration);
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
index cf84b7c..3e82eac 100644
--- a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
@@ -24,7 +24,8 @@
import android.telecom.Logging.Session;
import android.telecom.Logging.SessionManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/src/com/android/server/telecom/tests/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index f38618c..5378596 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -20,7 +20,8 @@
import android.telecom.Log;
import android.telecom.Logging.Session;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
index dc7d1fd..169d580 100644
--- a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
@@ -24,7 +24,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -44,7 +44,8 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.SystemStateHelper.SystemStateListener;
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 8bc1f2a..24b23af 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -23,6 +23,36 @@
import static android.Manifest.permission.READ_PHONE_NUMBERS;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.Manifest.permission.READ_SMS;
+import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.isA;
+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;
import android.Manifest;
import android.app.ActivityManager;
@@ -48,7 +78,8 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.telecom.ICallEventCallback;
import com.android.internal.telecom.ITelecomService;
@@ -58,11 +89,13 @@
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.InCallController;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.TelecomServiceImpl;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.voip.IncomingCallTransaction;
import com.android.server.telecom.voip.OutgoingCallTransaction;
import com.android.server.telecom.voip.TransactionManager;
@@ -76,40 +109,15 @@
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import java.lang.reflect.Method;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
-import static android.Manifest.permission.READ_SMS;
-import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.isA;
-import static org.mockito.Mockito.when;
-
@RunWith(JUnit4.class)
public class TelecomServiceImplTest extends TelecomTestCase {
@@ -122,7 +130,7 @@
public static class CallIntentProcessAdapterFake implements CallIntentProcessor.Adapter {
@Override
public void processOutgoingCallIntent(Context context, CallsManager callsManager,
- Intent intent, String callingPackage) {
+ Intent intent, String callingPackage, FeatureFlags flags) {
}
@@ -192,6 +200,10 @@
@Mock private ICallEventCallback mICallEventCallback;
@Mock private TransactionManager mTransactionManager;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+
+ @Mock private InCallController mInCallController;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
@@ -219,6 +231,7 @@
doReturn(mContext).when(mContext).getApplicationContext();
doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
+ when(mFakeCallsManager.getInCallController()).thenReturn(mInCallController);
doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
anyString());
when(mContext.checkCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS))
@@ -242,6 +255,8 @@
mDefaultDialerCache,
mSubscriptionManagerAdapter,
mSettingsSecureAdapter,
+ mFeatureFlags,
+ mTelephonyFeatureFlags,
mLock);
telecomServiceImpl.setTransactionManager(mTransactionManager);
telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
@@ -260,6 +275,8 @@
mPackageManager = mContext.getPackageManager();
when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
+ when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(true);
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
}
@Override
@@ -525,10 +542,64 @@
assertEquals(fullPHList,
mTSIBinder.getCallCapablePhoneAccounts(
- true, DEFAULT_DIALER_PACKAGE, null).getList());
+ true, DEFAULT_DIALER_PACKAGE, null, false).getList());
assertEquals(smallPHList,
mTSIBinder.getCallCapablePhoneAccounts(
- false, DEFAULT_DIALER_PACKAGE, null).getList());
+ false, DEFAULT_DIALER_PACKAGE, null, false).getList());
+ }
+
+ @SmallTest
+ @Test
+ public void testGetCallCapablePhoneAccountsAcrossProfiles() throws RemoteException {
+ List<PhoneAccountHandle> fullPHList = List.of(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+ List<PhoneAccountHandle> smallPHList = List.of(SIP_PA_HANDLE_17);
+
+ // Returns all accounts when getCallCapablePhoneAccounts is called with across user.
+ doReturn(fullPHList).when(mFakePhoneAccountRegistrar).getCallCapablePhoneAccounts(
+ nullable(String.class), anyBoolean(), nullable(UserHandle.class), eq(true));
+ // Returns one account when getCallCapablePhoneAccounts is called without across user.
+ doReturn(smallPHList).when(mFakePhoneAccountRegistrar).getCallCapablePhoneAccounts(
+ nullable(String.class), anyBoolean(), nullable(UserHandle.class), eq(false));
+ // With across user permission
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_USERS));
+
+ assertEquals(fullPHList,
+ mTSIBinder.getCallCapablePhoneAccounts(
+ true, DEFAULT_DIALER_PACKAGE, null, false).getList());
+
+ // Without across user permission
+ doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_USERS));
+
+ assertEquals(smallPHList,
+ mTSIBinder.getCallCapablePhoneAccounts(
+ true, DEFAULT_DIALER_PACKAGE, null, false).getList());
+
+ // Enabled the feature flag of the work profile split mode
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);
+
+ // With across user permission
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_PROFILES));
+
+ assertEquals(fullPHList,
+ mTSIBinder.getCallCapablePhoneAccounts(
+ true, DEFAULT_DIALER_PACKAGE, null, true).getList());
+ assertEquals(smallPHList,
+ mTSIBinder.getCallCapablePhoneAccounts(
+ true, DEFAULT_DIALER_PACKAGE, null, false).getList());
+
+ // Without across user permission
+ doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_PROFILES));
+
+ assertEquals(smallPHList,
+ mTSIBinder.getCallCapablePhoneAccounts(
+ true, DEFAULT_DIALER_PACKAGE, null, true).getList());
+ assertEquals(smallPHList,
+ mTSIBinder.getCallCapablePhoneAccounts(
+ true, DEFAULT_DIALER_PACKAGE, null, false).getList());
}
@SmallTest
@@ -540,7 +611,7 @@
argThat(new AnyStringIn(enforcedPermissions)), anyString());
assertThrows(SecurityException.class,
- () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null));
+ () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null, false));
}
@SmallTest
@@ -778,6 +849,62 @@
@SmallTest
@Test
+ public void testRegisterPhoneAccountSimultaneousCallingVerification() throws RemoteException {
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+ String packageNameToUse = "com.android.officialpackage";
+ PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+ packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+ PhoneAccountHandle phAllowedRestriction = new PhoneAccountHandle(new ComponentName(
+ packageNameToUse, "cs"), "test2", Binder.getCallingUserHandle());
+
+ PhoneAccount phoneAccountEmptyRestriction = makePhoneAccount(phHandle)
+ .setSimultaneousCallingRestriction(Collections.emptySet())
+ .build();
+ try {
+ mTSIBinder.registerPhoneAccount(phoneAccountEmptyRestriction, CALLING_PACKAGE);
+ verify(mFakePhoneAccountRegistrar).registerPhoneAccount(phoneAccountEmptyRestriction);
+ } catch (SecurityException e) {
+ fail("registerPhoneAccount must not throw a SecurityException if there is a "
+ + " restriction registered with the same package name.");
+ }
+
+ Set<PhoneAccountHandle> restriction = new HashSet<>(3);
+ restriction.add(phAllowedRestriction);
+ PhoneAccount phoneAccount = makePhoneAccount(phHandle)
+ .setSimultaneousCallingRestriction(restriction)
+ .build();
+
+ try {
+ mTSIBinder.registerPhoneAccount(phoneAccount, CALLING_PACKAGE);
+ verify(mFakePhoneAccountRegistrar).registerPhoneAccount(phoneAccount);
+ } catch (SecurityException e) {
+ fail("registerPhoneAccount must not throw a SecurityException if there is a "
+ + " restriction registered with the same package name.");
+ }
+
+ String anotherPackageName = "com.android.anotherpackage";
+ PhoneAccountHandle phDisallowedRestriction = new PhoneAccountHandle(new ComponentName(
+ anotherPackageName, "cs"), "test", Binder.getCallingUserHandle());
+ restriction.add(phDisallowedRestriction);
+ phoneAccount = makePhoneAccount(phHandle)
+ .setSimultaneousCallingRestriction(restriction)
+ .build();
+
+ try {
+ mTSIBinder.registerPhoneAccount(phoneAccount, CALLING_PACKAGE);
+ // there should not be another call to registerPhoneAccount
+ verify(mFakePhoneAccountRegistrar, times(1)).registerPhoneAccount(phoneAccount);
+ fail("registerPhoneAccount must throw a SecurityException if there is a "
+ + " restriction registered with a different package name.");
+ } catch (SecurityException e) {
+ //expected
+ }
+ }
+
+ @SmallTest
+ @Test
public void testRegisterPhoneAccountWithoutPermissionAnomalyReported() throws RemoteException {
PhoneAccountHandle handle = new PhoneAccountHandle(
new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
@@ -1040,6 +1167,7 @@
verify(mFakePhoneAccountRegistrar).getPhoneAccount(
TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle());
+ verify(mInCallController, never()).bindToServices(any());
addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
CallIntentProcessor.KEY_IS_INCOMING_CALL, extras,
TEL_PA_HANDLE_16, false);
@@ -1047,6 +1175,81 @@
@SmallTest
@Test
+ public void testAddNewIncomingFlagDisabledNoEarlyBinding() throws Exception {
+ when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(false);
+ PhoneAccount phoneAccount = makeSkipCallFilteringPhoneAccount(TEL_PA_HANDLE_16).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(TEL_PA_HANDLE_16));
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+ Bundle extras = createSampleExtras();
+
+ mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+ verify(mInCallController, never()).bindToServices(null);
+ }
+
+ @SmallTest
+ @Test
+ public void testAddNewIncomingCallEarlyBindingForNoCallFilterCalls() throws Exception {
+ PhoneAccount phoneAccount = makeSkipCallFilteringPhoneAccount(TEL_PA_HANDLE_16).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(TEL_PA_HANDLE_16));
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+ Bundle extras = createSampleExtras();
+
+ mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+ verify(mInCallController).bindToServices(null);
+ }
+
+ @SmallTest
+ @Test
+ public void testAddNewIncomingCallEarlyBindingNotEnableForNonWatchDevices() throws Exception {
+ PhoneAccount phoneAccount = makeSkipCallFilteringPhoneAccount(TEL_PA_HANDLE_16).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(TEL_PA_HANDLE_16));
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+ Bundle extras = createSampleExtras();
+
+ mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+ verify(mInCallController, never()).bindToServices(null);
+ }
+
+ @SmallTest
+ @Test
+ public void testAddNewIncomingCallEarlyBindingNotEnableForPhoneAccountHasCallFilters()
+ throws Exception {
+ PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_16).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(TEL_PA_HANDLE_16));
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+ Bundle extras = createSampleExtras();
+
+ mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+ verify(mInCallController, never()).bindToServices(null);
+ }
+
+
+ @SmallTest
+ @Test
public void testAddNewIncomingCallFailure() throws Exception {
try {
mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null, CALLING_PACKAGE);
@@ -1703,6 +1906,28 @@
verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
}
+ /**
+ * FeatureFlags is autogenerated code, so there could be a situation where something changes
+ * outside of Telecom control that breaks reflection. This test attempts to ensure that changes
+ * to auto-generated FeatureFlags code that breaks reflection are caught early.
+ */
+ @SmallTest
+ @Test
+ public void testFlagConfigReflectionWorks() {
+ try {
+ Method[] methods = FeatureFlags.class.getMethods();
+ for (Method m : methods) {
+ // test getting the name and invoking the flag code
+ String name = m.getName();
+ Object val = m.invoke(mFeatureFlags);
+ assertNotNull(name);
+ assertNotNull(val);
+ }
+ } catch (Exception e) {
+ fail("Reflection failed for FeatureFlags with error: " + e);
+ }
+ }
+
@SmallTest
@Test
public void testIsVoicemailNumber() throws Exception {
@@ -2144,6 +2369,12 @@
return new PhoneAccount.Builder(paHandle, "testLabel");
}
+ private PhoneAccount.Builder makeSkipCallFilteringPhoneAccount(PhoneAccountHandle paHandle) {
+ Bundle extras = new Bundle();
+ extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
+ return new PhoneAccount.Builder(paHandle, "testLabel").setExtras(extras);
+ }
+
private Bundle createSampleExtras() {
Bundle extras = new Bundle();
extras.putString("test_key", "test_value");
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index b962b2a..b7de84f 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -22,11 +22,11 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
@@ -69,6 +70,7 @@
import com.android.internal.telecom.IInCallAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -98,6 +100,7 @@
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.google.common.base.Predicate;
@@ -119,7 +122,7 @@
/**
* Implements mocks and functionality required to implement telecom system tests.
*/
-public class TelecomSystemTest extends TelecomTestCase {
+public class TelecomSystemTest extends TelecomTestCase{
private static final String CALLING_PACKAGE = TelecomSystemTest.class.getPackageName();
static final int TEST_POLL_INTERVAL = 10; // milliseconds
@@ -167,7 +170,7 @@
}
@Override
- public void showMissedCallNotification(CallInfo call) {
+ public void showMissedCallNotification(CallInfo call, @Nullable Uri uri) {
missedCallsNotified.add(call);
}
@@ -214,6 +217,10 @@
@Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
@Mock
BlockedNumbersAdapter mBlockedNumbersAdapter;
+ @Mock
+ CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+ @Mock
+ FeatureFlags mFeatureFlags;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -320,6 +327,20 @@
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
.build();
+ final PhoneAccount mPhoneAccountMultiUser =
+ PhoneAccount.builder(
+ new PhoneAccountHandle(
+ mConnectionServiceComponentNameA,
+ "id MU", UserHandle.of(12)),
+ "Phone account service MU")
+ .addSupportedUriScheme("tel")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING |
+ PhoneAccount.CAPABILITY_MULTI_USER)
+ .build();
+
ConnectionServiceFixture mConnectionServiceFixtureA;
ConnectionServiceFixture mConnectionServiceFixtureB;
Timeouts.Adapter mTimeoutsAdapter;
@@ -493,9 +514,11 @@
when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
when(mRoleManagerAdapter.getDefaultCallScreeningApp(any(UserHandle.class)))
.thenReturn(null);
+ when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(false);
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
- (context, phoneAccountRegistrar, defaultDialerCache, mDeviceIdleControllerAdapter)
+ (context, phoneAccountRegistrar, defaultDialerCache, mDeviceIdleControllerAdapter,
+ mFeatureFlag)
-> mMissedCallNotifier,
mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
headsetMediaButtonFactory,
@@ -518,7 +541,9 @@
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
int earpieceControl,
- Executor asyncTaskExecutor) {
+ Executor asyncTaskExecutor,
+ CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+ FeatureFlags featureFlags) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
@@ -528,15 +553,20 @@
// Force enable an earpiece for the end-to-end tests
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
mHandlerThread.getLooper(),
- Runnable::run /* async tasks as now sync for testing! */);
+ Runnable::run /* async tasks as now sync for testing! */,
+ communicationDeviceTracker,
+ featureFlags);
}
},
new CallAudioModeStateMachine.Factory() {
@Override
public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
- AudioManager am) {
+ AudioManager am, FeatureFlags featureFlags,
+ CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker
+ ) {
return new CallAudioModeStateMachine(systemStateHelper, am,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(), featureFlags,
+ callAudioCommunicationDeviceTracker);
}
},
mClockProxy,
@@ -549,7 +579,9 @@
}
}, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
Runnable::run,
- mBlockedNumbersAdapter);
+ Runnable::run,
+ mBlockedNumbersAdapter,
+ mFeatureFlags);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
@@ -583,6 +615,7 @@
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
+ mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountMultiUser);
mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 5353bc6..e8389a0 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -22,6 +22,9 @@
import androidx.test.InstrumentationRegistry;
+import com.android.server.telecom.flags.FeatureFlags;
+
+import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -33,6 +36,8 @@
public abstract class TelecomTestCase {
protected static final String TESTING_TAG = "Telecom-TEST";
protected Context mContext;
+ @Mock
+ FeatureFlags mFeatureFlags;
MockitoHelper mMockitoHelper = new MockitoHelper();
ComponentContextFixture mComponentContextFixture;
@@ -42,11 +47,12 @@
Log.setIsExtendedLoggingEnabled(true);
Log.setUnitTestingEnabled(true);
mMockitoHelper.setUp(InstrumentationRegistry.getContext(), getClass());
- mComponentContextFixture = new ComponentContextFixture();
+ MockitoAnnotations.initMocks(this);
+
+ mComponentContextFixture = new ComponentContextFixture(mFeatureFlags);
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
Log.setSessionContext(mComponentContextFixture.getTestDouble().getApplicationContext());
Log.getSessionManager().mCleanStaleSessions = null;
- MockitoAnnotations.initMocks(this);
}
public void tearDown() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index 3fc87a9..e58c6c4 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -16,7 +16,11 @@
package com.android.server.telecom.tests;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -39,11 +43,15 @@
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
+import androidx.test.filters.SmallTest;
+
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.ui.ToastFactory;
@@ -53,6 +61,8 @@
import com.android.server.telecom.voip.OutgoingCallTransaction;
import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
+import com.android.server.telecom.voip.VerifyCallStateChangeTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
import org.junit.After;
import org.junit.Before;
@@ -62,6 +72,11 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
public class TransactionTests extends TelecomTestCase {
@@ -250,6 +265,65 @@
isA(Boolean.class));
}
+ /**
+ * This test verifies if the ConnectionService call is NOT transitioned to the desired call
+ * state (within timeout period), Telecom will disconnect the call.
+ */
+ @SmallTest
+ @Test
+ public void testCallStateChangeTimesOut()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ when(mFeatureFlags.transactionalCsVerifier()).thenReturn(true);
+ VerifyCallStateChangeTransaction t = new VerifyCallStateChangeTransaction(mCallsManager,
+ mMockCall1, CallState.ON_HOLD, true);
+ // WHEN
+ setupHoldableCall();
+
+ // simulate the transaction being processed and the CompletableFuture timing out
+ t.processTransaction(null);
+ CompletableFuture<Integer> timeoutFuture = t.getCallStateOrTimeoutResult();
+ timeoutFuture.complete(VerifyCallStateChangeTransaction.FAILURE_CODE);
+
+ // THEN
+ verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl());
+ assertEquals(timeoutFuture.get().intValue(), VerifyCallStateChangeTransaction.FAILURE_CODE);
+ assertEquals(VoipCallTransactionResult.RESULT_FAILED,
+ t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult());
+ verify(mMockCall1, atLeastOnce()).removeCallStateListener(any());
+ verify(mCallsManager, times(1)).markCallAsDisconnected(eq(mMockCall1), any());
+ verify(mCallsManager, times(1)).markCallAsRemoved(eq(mMockCall1));
+ }
+
+ /**
+ * This test verifies that when an application transitions a call to the requested state,
+ * Telecom does not disconnect the call and transaction completes successfully.
+ */
+ @SmallTest
+ @Test
+ public void testCallStateIsSuccessfullyChanged()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ when(mFeatureFlags.transactionalCsVerifier()).thenReturn(true);
+ VerifyCallStateChangeTransaction t = new VerifyCallStateChangeTransaction(mCallsManager,
+ mMockCall1, CallState.ON_HOLD, true);
+ // WHEN
+ setupHoldableCall();
+
+ // simulate the transaction being processed and the setOnHold() being called / state change
+ t.processTransaction(null);
+ t.getCallStateListenerImpl().onCallStateChanged(CallState.ON_HOLD);
+ when(mMockCall1.getState()).thenReturn(CallState.ON_HOLD);
+
+ // THEN
+ verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl());
+ assertEquals(t.getCallStateOrTimeoutResult().get().intValue(),
+ VerifyCallStateChangeTransaction.SUCCESS_CODE);
+ assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult());
+ verify(mMockCall1, atLeastOnce()).removeCallStateListener(any());
+ verify(mCallsManager, never()).markCallAsDisconnected(eq(mMockCall1), any());
+ verify(mCallsManager, never()).markCallAsRemoved(eq(mMockCall1));
+ }
+
private Call createSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState, String id) {
when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
@@ -267,7 +341,8 @@
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
Call callSpy = Mockito.spy(call);
@@ -280,4 +355,12 @@
return callSpy;
}
+
+ private void setupHoldableCall(){
+ when(mMockCall1.getState()).thenReturn(CallState.ACTIVE);
+ when(mMockCall1.getConnectionServiceWrapper()).thenReturn(
+ mock(ConnectionServiceWrapper.class));
+ doNothing().when(mMockCall1).addCallStateListener(any());
+ doReturn(true).when(mMockCall1).removeCallStateListener(any());
+ }
}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index 84beedc..0ce70af 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -16,6 +16,26 @@
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.junit.Assert.fail;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
+import android.os.Process;
+import android.os.RemoteException;
+import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
+import android.telecom.VideoProfile;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteAdapter;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -23,26 +43,8 @@
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.telecom.CallAudioState;
-import android.telecom.DisconnectCause;
-import android.telecom.VideoProfile;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import com.android.server.telecom.CallAudioModeStateMachine;
-import com.android.server.telecom.CallAudioRouteStateMachine;
-
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
-
/**
* System tests for video-specific behavior in telecom.
* TODO: Add unit tests which ensure that auto-speakerphone does not occur when using a wired
@@ -258,13 +260,13 @@
*/
private void verifyAudioRoute(int expectedRoute) throws Exception {
// Capture all onCallAudioStateChanged callbacks to InCall.
- CallAudioRouteStateMachine carsm = mTelecomSystem.getCallsManager()
- .getCallAudioManager().getCallAudioRouteStateMachine();
+ CallAudioRouteAdapter cara = mTelecomSystem.getCallsManager()
+ .getCallAudioManager().getCallAudioRouteAdapter();
CallAudioModeStateMachine camsm = mTelecomSystem.getCallsManager()
.getCallAudioManager().getCallAudioModeStateMachine();
waitForHandlerAction(camsm.getHandler(), TEST_TIMEOUT);
final boolean[] success = {true};
- carsm.sendMessage(CallAudioRouteStateMachine.RUN_RUNNABLE, (Runnable) () -> {
+ cara.sendMessage(CallAudioRouteStateMachine.RUN_RUNNABLE, (Runnable) () -> {
ArgumentCaptor<CallAudioState> callAudioStateArgumentCaptor = ArgumentCaptor.forClass(
CallAudioState.class);
try {
@@ -277,7 +279,7 @@
assertEquals(expectedRoute, changes.get(changes.size() - 1).getRoute());
success[0] = true;
});
- waitForHandlerAction(carsm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(cara.getAdapterHandler(), TEST_TIMEOUT);
assertTrue(success[0]);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/VideoProfileTest.java b/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
index 5ee0414..b2a1c81 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
@@ -21,7 +21,8 @@
import static org.junit.Assert.assertTrue;
import android.telecom.VideoProfile;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
index 2b6c260..060e3ae 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
@@ -26,7 +26,8 @@
import android.os.IBinder;
import android.telecom.VideoProfile;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.Analytics;
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index 597924d..56fbf72 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -16,6 +16,41 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.net.Uri;
+import android.os.Build;
+import android.os.UserHandle;
+import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
+import android.telecom.InCallService;
+import android.telecom.InCallService.VideoCall;
+import android.telecom.VideoCallImpl;
+import android.telecom.VideoProfile;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.view.Surface;
+
+import androidx.test.filters.MediumTest;
+
+import com.android.server.telecom.CallsManager;
+
+import com.google.common.base.Predicate;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -26,48 +61,10 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.telecom.Call;
-import android.telecom.Connection;
-import android.telecom.Connection.VideoProvider;
-import android.telecom.DisconnectCause;
-import android.telecom.InCallService;
-import android.telecom.InCallService.VideoCall;
-import android.telecom.VideoCallImpl;
-import android.telecom.VideoProfile;
-import android.telecom.VideoProfile.CameraCapabilities;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.Surface;
-
-import com.google.common.base.Predicate;
-
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import com.android.server.telecom.CallsManager;
-
/**
* Performs tests of the {@link VideoProvider} and {@link VideoCall} APIs. Ensures that requests
* sent from an InCallService are routed through Telecom to a VideoProvider, and that callbacks are
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
index c66b0f7..7f7399c 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -16,6 +16,11 @@
package com.android.server.telecom.tests;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -38,7 +43,8 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.PhoneAccountHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
@@ -86,6 +92,31 @@
.thenReturn(true);
}
+ /**
+ * This test ensures VoipCallMonitor is passing the correct foregroundServiceTypes when starting
+ * foreground service delegation on behalf of a client.
+ */
+ @SmallTest
+ @Test
+ public void testVerifyForegroundServiceTypesBeingPassedToActivityManager() {
+ Call call = createTestCall("testCall", mHandle1User1);
+ ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor =
+ ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+
+ mMonitor.onCallAdded(call);
+
+ verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(
+ optionsCaptor.capture(), any(ServiceConnection.class));
+
+ assertEquals( FOREGROUND_SERVICE_TYPE_PHONE_CALL |
+ FOREGROUND_SERVICE_TYPE_MICROPHONE |
+ FOREGROUND_SERVICE_TYPE_CAMERA |
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+ optionsCaptor.getValue().mForegroundServiceTypes);
+
+ mMonitor.onCallRemoved(call);
+ }
+
@SmallTest
@Test
public void testStartMonitorForOneCall() {