Merge "Formalize BlockedNumberContract.SystemContract APIs" into main
diff --git a/Android.bp b/Android.bp
index 7e57a3f..68a8e39 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,12 +1,13 @@
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
genrule {
name: "statslog-telecom-java-gen",
tools: ["stats-log-api-gen"],
- cmd: "$(location stats-log-api-gen) --java $(out) --module telecom"
- + " --javaPackage com.android.server.telecom --javaClass TelecomStatsLog",
+ cmd: "$(location stats-log-api-gen) --java $(out) --module telecom" +
+ " --javaPackage com.android.server.telecom --javaClass TelecomStatsLog",
out: ["com/android/server/telecom/TelecomStatsLog.java"],
}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 90e4bd9..a9b6154 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -66,6 +66,7 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
android:label="Broadcast the call type/duration information"
diff --git a/OWNERS b/OWNERS
index 6f90d48..9c071f9 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,6 +1,6 @@
+# Bug component: 151185
breadley@google.com
tgunn@google.com
-xiaotonj@google.com
tjstuart@google.com
rgreenwalt@google.com
pmadapurmath@google.com
diff --git a/flags/Android.bp b/flags/Android.bp
index 386831c..3497db8 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -21,23 +21,23 @@
aconfig_declarations {
name: "telecom_flags",
package: "com.android.server.telecom.flags",
+ container: "system",
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",
+ "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
index dbacc08..6879d86 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "gen_anom_report_on_focus_timeout"
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index 21b83b2..3e01db9 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "voip_app_actions_support"
diff --git a/flags/telecom_bluetoothroutemanager_flags.aconfig b/flags/telecom_bluetoothroutemanager_flags.aconfig
index ddd8571..1df1e9b 100644
--- a/flags/telecom_bluetoothroutemanager_flags.aconfig
+++ b/flags/telecom_bluetoothroutemanager_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "use_actual_address_to_enter_connecting_state"
diff --git a/flags/telecom_broadcast_flags.aconfig b/flags/telecom_broadcast_flags.aconfig
index 348d574..de8dd27 100644
--- a/flags/telecom_broadcast_flags.aconfig
+++ b/flags/telecom_broadcast_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "is_new_outgoing_call_broadcast_unblocking"
diff --git a/flags/telecom_call_filtering_flags.aconfig b/flags/telecom_call_filtering_flags.aconfig
index 95e74ce..72f9db3 100644
--- a/flags/telecom_call_filtering_flags.aconfig
+++ b/flags/telecom_call_filtering_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "skip_filter_phone_account_perform_dnd_filter"
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
index b5ea6a2..27a4b22 100644
--- a/flags/telecom_call_flags.aconfig
+++ b/flags/telecom_call_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "transactional_cs_verifier"
diff --git a/flags/telecom_callaudiomodestatemachine_flags.aconfig b/flags/telecom_callaudiomodestatemachine_flags.aconfig
index b263113..1d81535 100644
--- a/flags/telecom_callaudiomodestatemachine_flags.aconfig
+++ b/flags/telecom_callaudiomodestatemachine_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "set_audio_mode_before_abandon_focus"
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index fe21c92..f5da045 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "available_routes_never_updated_after_set_system_audio_state"
diff --git a/flags/telecom_calllog_flags.aconfig b/flags/telecom_calllog_flags.aconfig
index 3ce7b63..593b7e5 100644
--- a/flags/telecom_calllog_flags.aconfig
+++ b/flags/telecom_calllog_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "telecom_log_external_wearable_calls"
diff --git a/flags/telecom_calls_manager_flags.aconfig b/flags/telecom_calls_manager_flags.aconfig
index 1a19480..cdfcc30 100644
--- a/flags/telecom_calls_manager_flags.aconfig
+++ b/flags/telecom_calls_manager_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "use_improved_listener_order"
diff --git a/flags/telecom_connection_service_wrapper_flags.aconfig b/flags/telecom_connection_service_wrapper_flags.aconfig
index 5f46c27..80a8dfe 100644
--- a/flags/telecom_connection_service_wrapper_flags.aconfig
+++ b/flags/telecom_connection_service_wrapper_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "updated_rcs_call_count_tracking"
diff --git a/flags/telecom_default_phone_account_flags.aconfig b/flags/telecom_default_phone_account_flags.aconfig
index 03f324c..e6badde 100644
--- a/flags/telecom_default_phone_account_flags.aconfig
+++ b/flags/telecom_default_phone_account_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "only_update_telephony_on_valid_sub_ids"
diff --git a/flags/telecom_incallservice_flags.aconfig b/flags/telecom_incallservice_flags.aconfig
index 1110ca4..08a82ba 100644
--- a/flags/telecom_incallservice_flags.aconfig
+++ b/flags/telecom_incallservice_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "early_binding_to_incall_service"
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
index 6def938..674a968 100644
--- a/flags/telecom_resolve_hidden_dependencies.aconfig
+++ b/flags/telecom_resolve_hidden_dependencies.aconfig
@@ -1,8 +1,9 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "telecom_resolve_hidden_dependencies"
namespace: "telecom"
description: "Mainland cleanup for hidden dependencies"
- bug: "b/324090590"
+ bug: "323414215"
}
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
index 54748d0..13577bb 100644
--- a/flags/telecom_ringer_flag_declarations.aconfig
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "use_device_provided_serialized_ringer_vibration"
diff --git a/flags/telecom_work_profile_flags.aconfig b/flags/telecom_work_profile_flags.aconfig
index 180af59..854568b 100644
--- a/flags/telecom_work_profile_flags.aconfig
+++ b/flags/telecom_work_profile_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "associated_user_refactor_for_work_profile"
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index bbcf858..45e3340 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -721,7 +721,7 @@
private static int getCarrierId(Context context) {
SubscriptionManager subscriptionManager =
- context.getSystemService(SubscriptionManager.class);
+ context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
List<SubscriptionInfo> subInfos = subscriptionManager.getActiveSubscriptionInfoList();
if (subInfos == null) {
return -1;
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
new file mode 100644
index 0000000..5037cf5
--- /dev/null
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom;
+
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED;
+import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED;
+import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF;
+import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON;
+
+import android.annotation.IntDef;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class AudioRoute {
+ public static class Factory {
+ private final ScheduledExecutorService mScheduledExecutorService =
+ new ScheduledThreadPoolExecutor(1);
+ private final CompletableFuture<AudioRoute> mAudioRouteFuture = new CompletableFuture<>();
+ public AudioRoute create(@AudioRouteType int type, String bluetoothAddress,
+ AudioManager audioManager) throws RuntimeException {
+ createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES);
+ try {
+ return mAudioRouteFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException("Error when creating requested audio route");
+ }
+ }
+ private void createRetry(@AudioRouteType int type, String bluetoothAddress,
+ AudioManager audioManager, int retryCount) {
+ if (retryCount == 0) {
+ mAudioRouteFuture.complete(null);
+ }
+
+ Log.i(this, "creating AudioRoute with type %s and address %s, retry count %d",
+ DEVICE_TYPE_STRINGS.get(type), bluetoothAddress, retryCount);
+ AudioDeviceInfo routeInfo = null;
+ List<AudioDeviceInfo> infos = audioManager.getAvailableCommunicationDevices();
+ List<Integer> possibleInfoTypes = AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.get(type);
+ for (AudioDeviceInfo info : infos) {
+ Log.i(this, "type: " + info.getType());
+ if (possibleInfoTypes != null && possibleInfoTypes.contains(info.getType())) {
+ if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
+ if (bluetoothAddress.equals(info.getAddress())) {
+ routeInfo = info;
+ break;
+ }
+ } else {
+ routeInfo = info;
+ break;
+ }
+ }
+ }
+ if (routeInfo == null) {
+ CompletableFuture<Boolean> future = new CompletableFuture<>();
+ mScheduledExecutorService.schedule(new Runnable() {
+ @Override
+ public void run() {
+ createRetry(type, bluetoothAddress, audioManager, retryCount - 1);
+ }
+ }, RETRY_TIME_DELAY, TimeUnit.MILLISECONDS);
+ } else {
+ mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo));
+ }
+ }
+ }
+
+ private static final long RETRY_TIME_DELAY = 500L;
+ private static final int MAX_CONNECTION_RETRIES = 2;
+ public static final int TYPE_INVALID = 0;
+ public static final int TYPE_EARPIECE = 1;
+ public static final int TYPE_WIRED = 2;
+ public static final int TYPE_SPEAKER = 3;
+ public static final int TYPE_DOCK = 4;
+ public static final int TYPE_BLUETOOTH_SCO = 5;
+ public static final int TYPE_BLUETOOTH_HA = 6;
+ public static final int TYPE_BLUETOOTH_LE = 7;
+ @IntDef(prefix = "TYPE", value = {
+ TYPE_INVALID,
+ TYPE_EARPIECE,
+ TYPE_WIRED,
+ TYPE_SPEAKER,
+ TYPE_DOCK,
+ TYPE_BLUETOOTH_SCO,
+ TYPE_BLUETOOTH_HA,
+ TYPE_BLUETOOTH_LE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioRouteType {}
+
+ private @AudioRouteType int mAudioRouteType;
+ private String mBluetoothAddress;
+ private AudioDeviceInfo mInfo;
+ public static final Set<Integer> BT_AUDIO_DEVICE_INFO_TYPES = Set.of(
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO
+ );
+
+ public static final Set<Integer> BT_AUDIO_ROUTE_TYPES = Set.of(
+ AudioRoute.TYPE_BLUETOOTH_SCO,
+ AudioRoute.TYPE_BLUETOOTH_HA,
+ AudioRoute.TYPE_BLUETOOTH_LE
+ );
+
+ public static final HashMap<Integer, String> DEVICE_TYPE_STRINGS;
+ static {
+ DEVICE_TYPE_STRINGS = new HashMap<>();
+ DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE");
+ DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET");
+ DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER");
+ DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK");
+ DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO");
+ DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA");
+ DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE");
+ }
+
+ public static final HashMap<Integer, Integer> DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE;
+ static {
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE = new HashMap<>();
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+ TYPE_EARPIECE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, TYPE_SPEAKER);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ TYPE_BLUETOOTH_SCO);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID,
+ TYPE_BLUETOOTH_HA);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET,
+ TYPE_BLUETOOTH_LE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ TYPE_BLUETOOTH_LE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ TYPE_BLUETOOTH_LE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK);
+ }
+
+ private static final HashMap<Integer, List<Integer>> AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE;
+ static {
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE = new HashMap<>();
+ List<Integer> earpieceDeviceInfoTypes = new ArrayList<>();
+ earpieceDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes);
+
+ List<Integer> wiredDeviceInfoTypes = new ArrayList<>();
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_ACCESSORY);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes);
+
+ List<Integer> speakerDeviceInfoTypes = new ArrayList<>();
+ speakerDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes);
+
+ List<Integer> dockDeviceInfoTypes = new ArrayList<>();
+ dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK);
+ dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK_ANALOG);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes);
+
+ List<Integer> bluetoothScoDeviceInfoTypes = new ArrayList<>();
+ bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+ bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes);
+
+ List<Integer> bluetoothHearingAidDeviceInfoTypes = new ArrayList<>();
+ bluetoothHearingAidDeviceInfoTypes.add(AudioDeviceInfo.TYPE_HEARING_AID);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA,
+ bluetoothHearingAidDeviceInfoTypes);
+
+ List<Integer> bluetoothLeDeviceInfoTypes = new ArrayList<>();
+ bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_SPEAKER);
+ bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_BROADCAST);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes);
+ }
+
+ int getType() {
+ return mAudioRouteType;
+ }
+
+ String getBluetoothAddress() {
+ return mBluetoothAddress;
+ }
+
+ // Invoked when entered pending route whose dest route is this route
+ void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
+ AudioManager audioManager) {
+ if (pendingAudioRoute.isActive() && !active) {
+ audioManager.clearCommunicationDevice();
+ } else if (active) {
+ if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
+ pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED);
+ } else if (mAudioRouteType == TYPE_SPEAKER) {
+ pendingAudioRoute.addMessage(SPEAKER_ON);
+ }
+ if (!audioManager.setCommunicationDevice(mInfo)) {
+ pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED);
+ }
+ }
+ }
+
+ void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
+ AudioManager audioManager) {
+ if (active) {
+ if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
+ pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED);
+ } else if (mAudioRouteType == TYPE_SPEAKER) {
+ pendingAudioRoute.addMessage(SPEAKER_OFF);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public AudioRoute(@AudioRouteType int type, String bluetoothAddress, AudioDeviceInfo info) {
+ mAudioRouteType = type;
+ mBluetoothAddress = bluetoothAddress;
+ mInfo = info;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof AudioRoute otherRoute)) {
+ return false;
+ }
+ if (mAudioRouteType != otherRoute.getType()) {
+ return false;
+ }
+ return !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) || mBluetoothAddress.equals(
+ otherRoute.getBluetoothAddress());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAudioRouteType, mBluetoothAddress);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[Type=" + DEVICE_TYPE_STRINGS.get(mAudioRouteType)
+ + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid")
+ + "]";
+ }
+}
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 5fc2414..3a05eb5 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import static com.android.server.telecom.AudioRoute.BT_AUDIO_DEVICE_INFO_TYPES;
+
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.AudioDeviceInfo;
@@ -28,7 +30,6 @@
import java.util.Arrays;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Semaphore;
/**
@@ -41,12 +42,6 @@
// 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;
@@ -101,7 +96,7 @@
mLock.tryAcquire();
}
// There is only one audio device type associated with each type of BT device.
- boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+ boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
audioDeviceType, isBtDevice, btDevice);
@@ -182,7 +177,7 @@
mLock.tryAcquire();
}
// There is only one audio device type associated with each type of BT device.
- boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+ boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
audioDeviceType, isBtDevice);
diff --git a/src/com/android/server/telecom/CallAudioRouteAdapter.java b/src/com/android/server/telecom/CallAudioRouteAdapter.java
index 7f7b43c..f76d47d 100644
--- a/src/com/android/server/telecom/CallAudioRouteAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRouteAdapter.java
@@ -1,15 +1,131 @@
package com.android.server.telecom;
+import android.bluetooth.BluetoothDevice;
import android.os.Handler;
import android.telecom.CallAudioState;
+import android.util.SparseArray;
import com.android.internal.util.IndentingPrintWriter;
public interface CallAudioRouteAdapter {
+ /** Valid values for msg.what */
+ int CONNECT_WIRED_HEADSET = 1;
+ int DISCONNECT_WIRED_HEADSET = 2;
+ int CONNECT_DOCK = 5;
+ int DISCONNECT_DOCK = 6;
+ int BLUETOOTH_DEVICE_LIST_CHANGED = 7;
+ int BT_ACTIVE_DEVICE_PRESENT = 8;
+ int BT_ACTIVE_DEVICE_GONE = 9;
+ int BT_DEVICE_ADDED = 10;
+ int BT_DEVICE_REMOVED = 11;
+
+ int SWITCH_EARPIECE = 1001;
+ int SWITCH_BLUETOOTH = 1002;
+ int SWITCH_HEADSET = 1003;
+ int SWITCH_SPEAKER = 1004;
+ // Wired headset, earpiece, or speakerphone, in that order of precedence.
+ int SWITCH_BASELINE_ROUTE = 1005;
+
+ // Messages denoting that the speakerphone was turned on/off. Used to update state when we
+ // weren't the ones who turned it on/off
+ int SPEAKER_ON = 1006;
+ int SPEAKER_OFF = 1007;
+
+ // Messages denoting that the streaming route switch request was sent.
+ int STREAMING_FORCE_ENABLED = 1008;
+ int STREAMING_FORCE_DISABLED = 1009;
+
+ int USER_SWITCH_EARPIECE = 1101;
+ int USER_SWITCH_BLUETOOTH = 1102;
+ int USER_SWITCH_HEADSET = 1103;
+ int USER_SWITCH_SPEAKER = 1104;
+ int USER_SWITCH_BASELINE_ROUTE = 1105;
+
+ int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
+
+ // These three messages indicate state changes that come from BluetoothRouteManager.
+ // They may be triggered by the BT stack doing something on its own or they may be sent after
+ // we request that the BT stack do something. Any logic for these messages should take into
+ // account the possibility that the event indicated has already been processed (i.e. handling
+ // should be idempotent).
+ int BT_AUDIO_DISCONNECTED = 1301;
+ int BT_AUDIO_CONNECTED = 1302;
+ int BT_AUDIO_PENDING = 1303;
+
+ int MUTE_ON = 3001;
+ int MUTE_OFF = 3002;
+ int TOGGLE_MUTE = 3003;
+ int MUTE_EXTERNALLY_CHANGED = 3004;
+
+ int SWITCH_FOCUS = 4001;
+
+ // Used in testing to execute verifications. Not compatible with subsessions.
+ int RUN_RUNNABLE = 9001;
+
+ // Used for PendingAudioRoute to notify audio switch success
+ int EXIT_PENDING_ROUTE = 10001;
+ // Used for PendingAudioRoute to notify audio switch timeout
+ int PENDING_ROUTE_TIMEOUT = 10002;
+ // Used for PendingAudioRoute to notify audio switch failed
+ int PENDING_ROUTE_FAILED = 10003;
+
+ /** Valid values for mAudioFocusType */
+ int NO_FOCUS = 1;
+ int ACTIVE_FOCUS = 2;
+ int RINGING_FOCUS = 3;
+
+ /** Valid arg for BLUETOOTH_DEVICE_LIST_CHANGED */
+ int DEVICE_CONNECTED = 1;
+ int DEVICE_DISCONNECTED = 2;
+
+ SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
+ put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
+ put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
+ put(CONNECT_DOCK, "CONNECT_DOCK");
+ put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
+ put(BLUETOOTH_DEVICE_LIST_CHANGED, "BLUETOOTH_DEVICE_LIST_CHANGED");
+ put(BT_ACTIVE_DEVICE_PRESENT, "BT_ACTIVE_DEVICE_PRESENT");
+ put(BT_ACTIVE_DEVICE_GONE, "BT_ACTIVE_DEVICE_GONE");
+ put(BT_DEVICE_ADDED, "BT_DEVICE_ADDED");
+ put(BT_DEVICE_REMOVED, "BT_DEVICE_REMOVED");
+
+ put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
+ put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
+ put(SWITCH_HEADSET, "SWITCH_HEADSET");
+ put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
+ put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
+ put(SPEAKER_ON, "SPEAKER_ON");
+ put(SPEAKER_OFF, "SPEAKER_OFF");
+
+ put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
+ put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
+ put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
+ put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
+ put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
+
+ put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
+
+ put(BT_AUDIO_DISCONNECTED, "BT_AUDIO_DISCONNECTED");
+ put(BT_AUDIO_CONNECTED, "BT_AUDIO_CONNECTED");
+ put(BT_AUDIO_PENDING, "BT_AUDIO_PENDING");
+
+ put(MUTE_ON, "MUTE_ON");
+ put(MUTE_OFF, "MUTE_OFF");
+ put(TOGGLE_MUTE, "TOGGLE_MUTE");
+ put(MUTE_EXTERNALLY_CHANGED, "MUTE_EXTERNALLY_CHANGED");
+
+ put(SWITCH_FOCUS, "SWITCH_FOCUS");
+
+ put(RUN_RUNNABLE, "RUN_RUNNABLE");
+
+ put(EXIT_PENDING_ROUTE, "EXIT_PENDING_ROUTE");
+ }};
+
void initialize();
void sendMessageWithSessionInfo(int message);
void sendMessageWithSessionInfo(int message, int arg);
void sendMessageWithSessionInfo(int message, int arg, String data);
+ void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice);
void sendMessage(int message, Runnable r);
void setCallAudioManager(CallAudioManager callAudioManager);
CallAudioState getCurrentCallAudioState();
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index f8c49bb..c1d7d0c 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -1,41 +1,226 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
package com.android.server.telecom;
+import static com.android.server.telecom.AudioRoute.BT_AUDIO_ROUTE_TYPES;
+import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
+import android.os.Message;
import android.telecom.CallAudioState;
+import android.telecom.Log;
+import android.telecom.Logging.Session;
+import android.util.ArrayMap;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
-public class CallAudioRouteController implements CallAudioRouteAdapter {
- private Handler mHandler;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
- public CallAudioRouteController() {
+public class CallAudioRouteController implements CallAudioRouteAdapter {
+ private static final long TIMEOUT_LIMIT = 2000L;
+ private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null);
+ private static final Map<Integer, Integer> ROUTE_MAP;
+ static {
+ ROUTE_MAP = new ArrayMap<>();
+ ROUTE_MAP.put(AudioRoute.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE);
+ ROUTE_MAP.put(AudioRoute.TYPE_WIRED, CallAudioState.ROUTE_WIRED_HEADSET);
+ ROUTE_MAP.put(AudioRoute.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER);
+ ROUTE_MAP.put(AudioRoute.TYPE_DOCK, CallAudioState.ROUTE_SPEAKER);
+ ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_SCO, CallAudioState.ROUTE_BLUETOOTH);
+ ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_HA, CallAudioState.ROUTE_BLUETOOTH);
+ ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_LE, CallAudioState.ROUTE_BLUETOOTH);
+ }
+
+ private final CallsManager mCallsManager;
+ private AudioManager mAudioManager;
+ private final Handler mHandler;
+ private final WiredHeadsetManager mWiredHeadsetManager;
+ private Set<AudioRoute> mAvailableRoutes;
+ private AudioRoute mCurrentRoute;
+ private AudioRoute mEarpieceWiredRoute;
+ private AudioRoute mSpeakerDockRoute;
+ private Map<AudioRoute, BluetoothDevice> mBluetoothRoutes;
+ private Map<Integer, AudioRoute> mTypeRoutes;
+ private PendingAudioRoute mPendingAudioRoute;
+ private AudioRoute.Factory mAudioRouteFactory;
+ private CallAudioState mCallAudioState;
+ private boolean mIsMute;
+ private boolean mIsPending;
+ private boolean mIsActive;
+
+ public CallAudioRouteController(
+ Context context,
+ CallsManager callsManager,
+ AudioRoute.Factory audioRouteFactory,
+ WiredHeadsetManager wiredHeadsetManager) {
+ mCallsManager = callsManager;
+ mAudioManager = context.getSystemService(AudioManager.class);
+ mAudioRouteFactory = audioRouteFactory;
+ mWiredHeadsetManager = wiredHeadsetManager;
+ mIsMute = false;
HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
+ mHandler = new Handler(handlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ preHandleMessage(msg);
+ String address;
+ BluetoothDevice bluetoothDevice;
+ @AudioRoute.AudioRouteType int type;
+ switch (msg.what) {
+ case BT_AUDIO_CONNECTED:
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtAudioActive(bluetoothDevice);
+ break;
+ case BT_AUDIO_DISCONNECTED:
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtAudioInactive(bluetoothDevice);
+ break;
+ case BT_DEVICE_ADDED:
+ type = msg.arg1;
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtConnected(type, bluetoothDevice);
+ break;
+ case BT_DEVICE_REMOVED:
+ type = msg.arg1;
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtDisconnected(type, bluetoothDevice);
+ break;
+ case BLUETOOTH_DEVICE_LIST_CHANGED:
+ break;
+ case BT_ACTIVE_DEVICE_PRESENT:
+ type = msg.arg1;
+ address = (String) ((SomeArgs) msg.obj).arg2;
+ handleBtActiveDevicePresent(type, address);
+ break;
+ case BT_ACTIVE_DEVICE_GONE:
+ type = msg.arg1;
+ handleBtActiveDeviceGone(type);
+ break;
+ case EXIT_PENDING_ROUTE:
+ handleExitPendingRoute();
+ break;
+ default:
+ break;
+ }
+ postHandleMessage(msg);
+ }
+ };
}
@Override
public void initialize() {
+ mAvailableRoutes = new HashSet<>();
+ mBluetoothRoutes = new ArrayMap<>();
+ mTypeRoutes = new ArrayMap<>();
+ mPendingAudioRoute = new PendingAudioRoute(this, mAudioManager);
+
+ int supportMask = calculateSupportedRouteMask();
+ if ((supportMask & CallAudioState.ROUTE_SPEAKER) != 0) {
+ // Create spekaer routes
+ mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_SPEAKER, null,
+ mAudioManager);
+ if (mSpeakerDockRoute == null) {
+ Log.w(this, "Can't find available audio device info for route TYPE_SPEAKER");
+ } else {
+ mTypeRoutes.put(AudioRoute.TYPE_SPEAKER, mSpeakerDockRoute);
+ mAvailableRoutes.add(mSpeakerDockRoute);
+ }
+ }
+
+ if ((supportMask & CallAudioState.ROUTE_WIRED_HEADSET) != 0) {
+ // Create wired headset routes
+ mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null,
+ mAudioManager);
+ if (mEarpieceWiredRoute == null) {
+ Log.w(this, "Can't find available audio device info for route TYPE_WIRED_HEADSET");
+ } else {
+ mTypeRoutes.put(AudioRoute.TYPE_WIRED, mEarpieceWiredRoute);
+ mAvailableRoutes.add(mEarpieceWiredRoute);
+ }
+ } else if ((supportMask & CallAudioState.ROUTE_EARPIECE) != 0) {
+ // Create earpiece routes
+ mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null,
+ mAudioManager);
+ if (mEarpieceWiredRoute == null) {
+ Log.w(this, "Can't find available audio device info for route TYPE_EARPIECE");
+ } else {
+ mTypeRoutes.put(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute);
+ mAvailableRoutes.add(mEarpieceWiredRoute);
+ }
+ }
+
+ // set current route
+ if (mEarpieceWiredRoute != null) {
+ mCurrentRoute = mEarpieceWiredRoute;
+ } else {
+ mCurrentRoute = mSpeakerDockRoute;
+ }
+ mIsActive = false;
+ mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()),
+ supportMask, null, new HashSet<>());
}
@Override
public void sendMessageWithSessionInfo(int message) {
+ sendMessageWithSessionInfo(message, 0, (String) null);
}
@Override
public void sendMessageWithSessionInfo(int message, int arg) {
-
+ sendMessageWithSessionInfo(message, arg, (String) null);
}
@Override
public void sendMessageWithSessionInfo(int message, int arg, String data) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Log.createSubsession();
+ args.arg2 = data;
+ sendMessage(message, arg, 0, args);
+ }
+ @Override
+ public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Log.createSubsession();
+ args.arg2 = bluetoothDevice;
+ sendMessage(message, arg, 0, args);
}
@Override
public void sendMessage(int message, Runnable r) {
+ r.run();
+ }
+ private void sendMessage(int what, int arg1, int arg2, Object obj) {
+ mHandler.sendMessage(Message.obtain(mHandler, what, arg1, arg2, obj));
}
@Override
@@ -49,7 +234,7 @@
@Override
public boolean isHfpDeviceAvailable() {
- return false;
+ return !mBluetoothRoutes.isEmpty();
}
@Override
@@ -59,6 +244,287 @@
@Override
public void dump(IndentingPrintWriter pw) {
+ }
+ private void preHandleMessage(Message msg) {
+ if (msg.obj instanceof SomeArgs) {
+ Session session = (Session) ((SomeArgs) msg.obj).arg1;
+ String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
+ Log.continueSession(session, "CARC.pM_" + messageCodeName);
+ Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
+ }
+ }
+
+ private void postHandleMessage(Message msg) {
+ Log.endSession();
+ if (msg.obj instanceof SomeArgs) {
+ ((SomeArgs) msg.obj).recycle();
+ }
+ }
+
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ public boolean isPending() {
+ return mIsPending;
+ }
+
+ private void routeTo(boolean active, AudioRoute destRoute) {
+ if (mIsPending) {
+ if (destRoute.equals(mPendingAudioRoute.getDestRoute()) && (mIsActive == active)) {
+ return;
+ }
+ Log.i(this, "Override current pending route destination from %s(active=%b) to "
+ + "%s(active=%b)",
+ mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
+ // override pending route while keep waiting for still pending messages for the
+ // previous pending route
+ mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
+ mPendingAudioRoute.setDestRoute(active, destRoute);
+ } else {
+ if (mCurrentRoute.equals(destRoute) && (mIsActive = active)) {
+ return;
+ }
+ Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
+ mIsActive, destRoute, active);
+ // route to pending route
+ if (mAvailableRoutes.contains(mCurrentRoute)) {
+ mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
+ } else {
+ // Avoid waiting for pending messages for an unavailable route
+ mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
+ }
+ mPendingAudioRoute.setDestRoute(active, destRoute);
+ mIsPending = true;
+ }
+ mPendingAudioRoute.evaluatePendingState();
+ postTimeoutMessage();
+ }
+
+ private void postTimeoutMessage() {
+ // reset timeout handler
+ mHandler.removeMessages(PENDING_ROUTE_TIMEOUT);
+ mHandler.postDelayed(() -> mHandler.sendMessage(
+ Message.obtain(mHandler, PENDING_ROUTE_TIMEOUT)), TIMEOUT_LIMIT);
+ }
+
+ private void handleBtAudioActive(BluetoothDevice bluetoothDevice) {
+ if (mIsPending) {
+ if (Objects.equals(mPendingAudioRoute.getDestRoute().getBluetoothAddress(),
+ bluetoothDevice.getAddress())) {
+ mPendingAudioRoute.onMessageReceived(BT_AUDIO_CONNECTED);
+ }
+ } else {
+ // ignore, not triggered by telecom
+ }
+ }
+
+ private void handleBtAudioInactive(BluetoothDevice bluetoothDevice) {
+ if (mIsPending) {
+ if (Objects.equals(mPendingAudioRoute.getOrigRoute().getBluetoothAddress(),
+ bluetoothDevice.getAddress())) {
+ mPendingAudioRoute.onMessageReceived(BT_AUDIO_DISCONNECTED);
+ }
+ } else {
+ // ignore, not triggered by telecom
+ }
+ }
+
+ private void handleBtConnected(@AudioRoute.AudioRouteType int type,
+ BluetoothDevice bluetoothDevice) {
+ AudioRoute bluetoothRoute = null;
+ bluetoothRoute = mAudioRouteFactory.create(type, bluetoothDevice.getAddress(),
+ mAudioManager);
+ if (bluetoothRoute == null) {
+ Log.w(this, "Can't find available audio device info for route type:"
+ + AudioRoute.DEVICE_TYPE_STRINGS.get(type));
+ } else {
+ Log.i(this, "bluetooth route added: " + bluetoothRoute);
+ mAvailableRoutes.add(bluetoothRoute);
+ mBluetoothRoutes.put(bluetoothRoute, bluetoothDevice);
+ onAvailableRoutesChanged();
+ }
+ }
+
+ private void handleBtDisconnected(@AudioRoute.AudioRouteType int type,
+ BluetoothDevice bluetoothDevice) {
+ // Clean up unavailable routes
+ AudioRoute bluetoothRoute = getBluetoothRoute(type, bluetoothDevice.getAddress());
+ if (bluetoothRoute != null) {
+ Log.i(this, "bluetooth route removed: " + bluetoothRoute);
+ mBluetoothRoutes.remove(bluetoothRoute);
+ mAvailableRoutes.remove(bluetoothRoute);
+ onAvailableRoutesChanged();
+ }
+
+ // Fallback to an available route
+ if (Objects.equals(mCurrentRoute, bluetoothRoute)) {
+ // fallback policy
+ AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
+ if (destRoute != null && mAvailableRoutes.contains(destRoute)) {
+ routeTo(mIsActive, destRoute);
+ } else {
+ routeTo(mIsActive, getPreferredAudioRouteFromDefault(true/* includeBluetooth */));
+ }
+ }
+ }
+
+ private void handleBtActiveDevicePresent(@AudioRoute.AudioRouteType int type,
+ String deviceAddress) {
+ AudioRoute bluetoothRoute = getBluetoothRoute(type, deviceAddress);
+ if (bluetoothRoute != null) {
+ Log.i(this, "request to route to bluetooth route: %s(active=%b)", bluetoothRoute,
+ mIsActive);
+ routeTo(mIsActive, bluetoothRoute);
+ }
+ }
+
+ private void handleBtActiveDeviceGone(@AudioRoute.AudioRouteType int type) {
+ if ((mIsPending && mPendingAudioRoute.getDestRoute().getType() == type)
+ || (!mIsPending && mCurrentRoute.getType() == type)) {
+ // Fallback to an available route
+ AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
+ if (destRoute != null && mAvailableRoutes.contains(destRoute)) {
+ routeTo(mIsActive, destRoute);
+ } else {
+ routeTo(mIsActive, getPreferredAudioRouteFromDefault(false/* includeBluetooth */));
+ }
+ onAvailableRoutesChanged();
+ }
+ }
+
+ public void handleExitPendingRoute() {
+ if (mIsPending) {
+ Log.i(this, "Exit pending route and enter %s(active=%b)",
+ mPendingAudioRoute.getDestRoute(), mPendingAudioRoute.isActive());
+ mCurrentRoute = mPendingAudioRoute.getDestRoute();
+ mIsActive = mPendingAudioRoute.isActive();
+ mIsPending = false;
+ onCurrentRouteChanged();
+ }
+ }
+
+ private void onCurrentRouteChanged() {
+ BluetoothDevice activeBluetoothDevice = null;
+ int route = ROUTE_MAP.get(mCurrentRoute.getType());
+ if (route == CallAudioState.ROUTE_BLUETOOTH) {
+ activeBluetoothDevice = mBluetoothRoutes.get(mCurrentRoute);
+ }
+ updateCallAudioState(new CallAudioState(mIsMute, route,
+ mCallAudioState.getSupportedRouteMask(), activeBluetoothDevice,
+ mCallAudioState.getSupportedBluetoothDevices()));
+ }
+
+ private void onAvailableRoutesChanged() {
+ int routeMask = 0;
+ Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>();
+ for (AudioRoute route : mAvailableRoutes) {
+ routeMask |= ROUTE_MAP.get(route.getType());
+ if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) {
+ availableBluetoothDevices.add(mBluetoothRoutes.get(route));
+ }
+ }
+ updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
+ mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
+ }
+
+ private void updateCallAudioState(CallAudioState callAudioState) {
+ CallAudioState oldState = mCallAudioState;
+ mCallAudioState = callAudioState;
+ mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
+ }
+
+ private AudioRoute getPreferredAudioRouteFromStrategy() {
+ // Get audio produce strategy
+ AudioProductStrategy strategy = null;
+ final AudioAttributes attr = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .build();
+ List<AudioProductStrategy> strategies = AudioManager.getAudioProductStrategies();
+ for (AudioProductStrategy s : strategies) {
+ if (s.supportsAudioAttributes(attr)) {
+ strategy = s;
+ }
+ }
+ if (strategy == null) {
+ return null;
+ }
+
+ // Get preferred device
+ AudioDeviceAttributes deviceAttr = mAudioManager.getPreferredDeviceForStrategy(strategy);
+ if (deviceAttr == null) {
+ return null;
+ }
+
+ // Get corresponding audio route
+ @AudioRoute.AudioRouteType int type = AudioRoute.DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.get(
+ deviceAttr.getType());
+ if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
+ return getBluetoothRoute(type, deviceAttr.getAddress());
+ } else {
+ return mTypeRoutes.get(deviceAttr.getType());
+
+ }
+ }
+
+ public AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth) {
+ if (mBluetoothRoutes.isEmpty() || !includeBluetooth) {
+ return mEarpieceWiredRoute != null ? mEarpieceWiredRoute : mSpeakerDockRoute;
+ } else {
+ // Most recent active route will always be the last in the array
+ return mBluetoothRoutes.keySet().stream().toList().get(mBluetoothRoutes.size() - 1);
+ }
+ }
+
+ private int calculateSupportedRouteMask() {
+ int routeMask = CallAudioState.ROUTE_SPEAKER;
+
+ if (mWiredHeadsetManager.isPluggedIn()) {
+ routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
+ } else {
+ AudioDeviceInfo[] deviceList = mAudioManager.getDevices(
+ AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device: deviceList) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
+ routeMask |= CallAudioState.ROUTE_EARPIECE;
+ break;
+ }
+ }
+ }
+ return routeMask;
+ }
+
+ public Set<AudioRoute> getAvailableRoutes() {
+ return mAvailableRoutes;
+ }
+
+ public AudioRoute getCurrentRoute() {
+ return mCurrentRoute;
+ }
+
+ private AudioRoute getBluetoothRoute(@AudioRoute.AudioRouteType int audioRouteType,
+ String address) {
+ for (AudioRoute route : mBluetoothRoutes.keySet()) {
+ if (route.getType() == audioRouteType && route.getBluetoothAddress().equals(address)) {
+ return route;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ public void setAudioManager(AudioManager audioManager) {
+ mAudioManager = audioManager;
+ }
+
+ @VisibleForTesting
+ public void setAudioRouteFactory(AudioRoute.Factory audioRouteFactory) {
+ mAudioRouteFactory = audioRouteFactory;
+ }
+
+ @VisibleForTesting
+ public void setActive(boolean active) {
+ mIsActive = active;
}
}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index c0bb50e..26c25e8 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -120,63 +120,6 @@
/** Direct the audio stream through another device. */
public static final int ROUTE_STREAMING = CallAudioState.ROUTE_STREAMING;
- /** Valid values for msg.what */
- public static final int CONNECT_WIRED_HEADSET = 1;
- public static final int DISCONNECT_WIRED_HEADSET = 2;
- public static final int CONNECT_DOCK = 5;
- public static final int DISCONNECT_DOCK = 6;
- public static final int BLUETOOTH_DEVICE_LIST_CHANGED = 7;
- public static final int BT_ACTIVE_DEVICE_PRESENT = 8;
- public static final int BT_ACTIVE_DEVICE_GONE = 9;
-
- public static final int SWITCH_EARPIECE = 1001;
- public static final int SWITCH_BLUETOOTH = 1002;
- public static final int SWITCH_HEADSET = 1003;
- public static final int SWITCH_SPEAKER = 1004;
- // Wired headset, earpiece, or speakerphone, in that order of precedence.
- public static final int SWITCH_BASELINE_ROUTE = 1005;
-
- // Messages denoting that the speakerphone was turned on/off. Used to update state when we
- // weren't the ones who turned it on/off
- public static final int SPEAKER_ON = 1006;
- public static final int SPEAKER_OFF = 1007;
-
- // Messages denoting that the streaming route switch request was sent.
- public static final int STREAMING_FORCE_ENABLED = 1008;
- public static final int STREAMING_FORCE_DISABLED = 1009;
-
- public static final int USER_SWITCH_EARPIECE = 1101;
- public static final int USER_SWITCH_BLUETOOTH = 1102;
- public static final int USER_SWITCH_HEADSET = 1103;
- public static final int USER_SWITCH_SPEAKER = 1104;
- public static final int USER_SWITCH_BASELINE_ROUTE = 1105;
-
- public static final int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
-
- // These three messages indicate state changes that come from BluetoothRouteManager.
- // They may be triggered by the BT stack doing something on its own or they may be sent after
- // we request that the BT stack do something. Any logic for these messages should take into
- // account the possibility that the event indicated has already been processed (i.e. handling
- // should be idempotent).
- public static final int BT_AUDIO_DISCONNECTED = 1301;
- public static final int BT_AUDIO_CONNECTED = 1302;
- public static final int BT_AUDIO_PENDING = 1303;
-
- public static final int MUTE_ON = 3001;
- public static final int MUTE_OFF = 3002;
- public static final int TOGGLE_MUTE = 3003;
- public static final int MUTE_EXTERNALLY_CHANGED = 3004;
-
- public static final int SWITCH_FOCUS = 4001;
-
- // Used in testing to execute verifications. Not compatible with subsessions.
- public static final int RUN_RUNNABLE = 9001;
-
- /** Valid values for mAudioFocusType */
- public static final int NO_FOCUS = 1;
- public static final int ACTIVE_FOCUS = 2;
- public static final int RINGING_FOCUS = 3;
-
/** Valid values for the first argument for SWITCH_BASELINE_ROUTE */
public static final int NO_INCLUDE_BLUETOOTH_IN_BASELINE = 0;
public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1;
@@ -189,45 +132,6 @@
put(CallAudioState.ROUTE_WIRED_HEADSET, LogUtils.Events.AUDIO_ROUTE_HEADSET);
}};
- private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
- put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
- put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
- put(CONNECT_DOCK, "CONNECT_DOCK");
- put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
- put(BLUETOOTH_DEVICE_LIST_CHANGED, "BLUETOOTH_DEVICE_LIST_CHANGED");
- put(BT_ACTIVE_DEVICE_PRESENT, "BT_ACTIVE_DEVICE_PRESENT");
- put(BT_ACTIVE_DEVICE_GONE, "BT_ACTIVE_DEVICE_GONE");
-
- put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
- put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
- put(SWITCH_HEADSET, "SWITCH_HEADSET");
- put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
- put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
- put(SPEAKER_ON, "SPEAKER_ON");
- put(SPEAKER_OFF, "SPEAKER_OFF");
-
- put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
- put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
- put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
- put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
- put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
-
- put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
-
- put(BT_AUDIO_DISCONNECTED, "BT_AUDIO_DISCONNECTED");
- put(BT_AUDIO_CONNECTED, "BT_AUDIO_CONNECTED");
- put(BT_AUDIO_PENDING, "BT_AUDIO_PENDING");
-
- put(MUTE_ON, "MUTE_ON");
- put(MUTE_OFF, "MUTE_OFF");
- put(TOGGLE_MUTE, "TOGGLE_MUTE");
- put(MUTE_EXTERNALLY_CHANGED, "MUTE_EXTERNALLY_CHANGED");
-
- put(SWITCH_FOCUS, "SWITCH_FOCUS");
-
- put(RUN_RUNNABLE, "RUN_RUNNABLE");
- }};
-
private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
@@ -1737,11 +1641,11 @@
}
public void sendMessageWithSessionInfo(int message, int arg) {
- sendMessageWithSessionInfo(message, arg, null);
+ sendMessageWithSessionInfo(message, arg, (String) null);
}
public void sendMessageWithSessionInfo(int message) {
- sendMessageWithSessionInfo(message, 0, null);
+ sendMessageWithSessionInfo(message, 0, (String) null);
}
public void sendMessageWithSessionInfo(int message, int arg, String data) {
@@ -1751,6 +1655,10 @@
sendMessage(message, arg, 0, args);
}
+ public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
+ // ignore, only used in CallAudioRouteController
+ }
+
@Override
public void sendMessage(int message, Runnable r) {
super.sendMessage(message, r);
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 2570aef..de7d84a 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -646,9 +646,11 @@
featureFlags
);
} else {
- callAudioRouteAdapter = new CallAudioRouteController();
+ callAudioRouteAdapter = new CallAudioRouteController(
+ context, this, new AudioRoute.Factory(), wiredHeadsetManager);
}
callAudioRouteAdapter.initialize();
+ bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter);
CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
new CallAudioRoutePeripheralAdapter(
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
new file mode 100644
index 0000000..5fa3048
--- /dev/null
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom;
+
+import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED;
+
+import android.media.AudioManager;
+
+import java.util.ArrayList;
+
+/**
+ * Used to represent the intermediate state during audio route switching.
+ * Usually, audio route switching start with a communication device setting request to audio
+ * framework and will be completed with corresponding success broadcasts or messages. Instance of
+ * this class is responsible for tracking the pending success signals according to the original
+ * audio route and the destination audio route of this switching.
+ */
+public class PendingAudioRoute {
+ private CallAudioRouteController mCallAudioRouteController;
+ private AudioManager mAudioManager;
+ /**
+ * The {@link AudioRoute} that this pending audio switching started with
+ */
+ private AudioRoute mOrigRoute;
+ /**
+ * The expected destination {@link AudioRoute} of this pending audio switching, can be changed
+ * by new switching request during the ongoing switching
+ */
+ private AudioRoute mDestRoute;
+ private ArrayList<Integer> mPendingMessages;
+ private boolean mActive;
+ PendingAudioRoute(CallAudioRouteController controller, AudioManager audioManager) {
+ mCallAudioRouteController = controller;
+ mAudioManager = audioManager;
+ mPendingMessages = new ArrayList<>();
+ mActive = false;
+ }
+
+ void setOrigRoute(boolean active, AudioRoute origRoute) {
+ origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager);
+ mOrigRoute = origRoute;
+ }
+
+ AudioRoute getOrigRoute() {
+ return mOrigRoute;
+ }
+
+ void setDestRoute(boolean active, AudioRoute destRoute) {
+ destRoute.onDestRouteAsPendingRoute(active, this, mAudioManager);
+ mActive = active;
+ mDestRoute = destRoute;
+ }
+
+ AudioRoute getDestRoute() {
+ return mDestRoute;
+ }
+
+ public void addMessage(int message) {
+ mPendingMessages.add(message);
+ }
+
+ public void onMessageReceived(int message) {
+ if (message == PENDING_ROUTE_FAILED) {
+ // Fallback to base route
+ //TODO: Replace getPreferredAudioRouteFromDefault by getBaseRoute when available and
+ // make the replaced one private
+ mDestRoute = mCallAudioRouteController.getPreferredAudioRouteFromDefault(true);
+ mCallAudioRouteController.sendMessageWithSessionInfo(
+ CallAudioRouteAdapter.EXIT_PENDING_ROUTE);
+ }
+
+ // Removes the first occurrence of the specified message from this list, if it is present.
+ mPendingMessages.remove((Object) message);
+ evaluatePendingState();
+ }
+
+ public void evaluatePendingState() {
+ if (mPendingMessages.isEmpty()) {
+ mCallAudioRouteController.sendMessageWithSessionInfo(
+ CallAudioRouteAdapter.EXIT_PENDING_ROUTE);
+ }
+ }
+
+ public boolean isActive() {
+ return mActive;
+ }
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 0ce0094..08566c9 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -2506,22 +2506,28 @@
* @param packageName the package name of the app to check calls for.
* @param userHandle the user handle on which to check for calls.
* @param callingPackage The caller's package name.
+ * @param detectForAllUsers indicates if calls should be detected across all users. If it is
+ * set to true, the userHandle parameter is disregarded.
* @return {@code true} if there are ongoing calls, {@code false} otherwise.
*/
@Override
public boolean isInSelfManagedCall(String packageName, UserHandle userHandle,
- String callingPackage, boolean hasCrossUserAccess) {
+ String callingPackage, boolean detectForAllUsers) {
try {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"READ_PRIVILEGED_PHONE_STATE required.");
- enforceInAppCrossUserPermission();
+ // Ensure that the caller has the INTERACT_ACROSS_USERS permission if it's trying
+ // to access calls that don't belong to it.
+ if (detectForAllUsers || !Binder.getCallingUserHandle().equals(userHandle)) {
+ enforceInAppCrossUserPermission();
+ }
Log.startSession("TSI.iISMC", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
return mCallsManager.isInSelfManagedCallCrossUsers(
- packageName, userHandle, hasCrossUserAccess);
+ packageName, userHandle, detectForAllUsers);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index d2521ac..6d80cd5 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -16,6 +16,15 @@
package com.android.server.telecom.bluetooth;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -32,11 +41,11 @@
import android.telecom.Logging.Session;
import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.AudioRoute;
import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.CallAudioRouteAdapter;
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;
+import com.android.server.telecom.flags.Flags;
public class BluetoothStateReceiver extends BroadcastReceiver {
@@ -61,6 +70,7 @@
private final BluetoothDeviceManager mBluetoothDeviceManager;
private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
private FeatureFlags mFeatureFlags;
+ private CallAudioRouteAdapter mCallAudioRouteAdapter;
public void onReceive(Context context, Intent intent) {
Log.startSession("BSR.oR");
@@ -106,14 +116,24 @@
args.arg2 = device.getAddress();
switch (bluetoothHeadsetAudioState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- if (!mIsInCall) {
- Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call");
- return;
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
+ device);
+ } else {
+ if (!mIsInCall) {
+ Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call");
+ return;
+ }
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
}
- mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ device);
+ } else {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ }
break;
}
}
@@ -131,12 +151,16 @@
}
int deviceType;
+ @AudioRoute.AudioRouteType int audioRouteType;
if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_LE;
} else if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_HA;
} else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_SCO;
} else {
Log.w(LOG_TAG, "handleConnectionStateChanged: %s invalid device type", device);
return;
@@ -147,10 +171,20 @@
device.getAddress(), bluetoothHeadsetState);
if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
- mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_ADDED,
+ audioRouteType, device);
+ } else {
+ mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
+ }
} else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
|| bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
- mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_REMOVED,
+ audioRouteType, device);
+ } else {
+ mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
+ }
}
}
@@ -159,12 +193,16 @@
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
int deviceType;
+ @AudioRoute.AudioRouteType int audioRouteType;
if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_LE;
} else if (BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_HA;
} else if (BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_SCO;
} else {
Log.w(LOG_TAG, "handleActiveDeviceChanged: %s invalid device type", device);
return;
@@ -173,72 +211,84 @@
Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
BluetoothDeviceManager.getDeviceTypeString(deviceType));
- mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType);
- if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID ||
- deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
- Session session = Log.createSubsession();
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = session;
+ if (Flags.useRefactoredAudioRouteSwitching()) {
if (device == null) {
- mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
+ audioRouteType);
} else {
- if (!mIsInCall) {
- Log.i(LOG_TAG, "Ignoring audio on since we're not in a call");
- return;
- }
- 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
- */
- 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);
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ audioRouteType, device.getAddress());
+ }
+ } else {
+ mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType);
+ if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID ||
+ deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
+ Session session = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = session;
+ if (device == null) {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ } else {
+ if (!mIsInCall) {
+ Log.i(LOG_TAG, "Ignoring audio on since we're not in a call");
return;
}
- } else {
- boolean isHearingAidSetForCommunication =
- mFeatureFlags.callAudioCommunicationDeviceRefactor()
- ? mCommunicationDeviceTracker.setCommunicationDevice(
- AudioDeviceInfo.TYPE_HEARING_AID, null)
- : mBluetoothDeviceManager.setHearingAidCommunicationDevice();
- /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
- if (!isHearingAidSetForCommunication) {
- Log.w(LOG_TAG,
- "Device %s cannot be use as hearing aid communication device.",
+ 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
+ */
+ 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);
+ }
} else {
- mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
+ boolean isHearingAidSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_HEARING_AID, null)
+ : mBluetoothDeviceManager
+ .setHearingAidCommunicationDevice();
+ /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
+ if (!isHearingAidSetForCommunication) {
+ Log.w(LOG_TAG,
+ "Device %s cannot be use as hearing aid communication device.",
+ device);
+ } else {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
+ }
}
}
- }
+ }
}
}
@@ -259,4 +309,8 @@
public void setIsInCall(boolean isInCall) {
mIsInCall = isInCall;
}
+
+ public void setCallAudioRouteAdapter(CallAudioRouteAdapter adapter) {
+ mCallAudioRouteAdapter = adapter;
+ }
}
diff --git a/testapps/Android.bp b/testapps/Android.bp
index 11ea474..45ea753 100644
--- a/testapps/Android.bp
+++ b/testapps/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/callaudiotest/Android.bp b/testapps/callaudiotest/Android.bp
index 81164e6..d996236 100644
--- a/testapps/callaudiotest/Android.bp
+++ b/testapps/callaudiotest/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/carmodedialer/Android.bp b/testapps/carmodedialer/Android.bp
index 9f65b8c..f142bf4 100644
--- a/testapps/carmodedialer/Android.bp
+++ b/testapps/carmodedialer/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/companionapp/Android.bp b/testapps/companionapp/Android.bp
index 8718b37..84ee4d3 100644
--- a/testapps/companionapp/Android.bp
+++ b/testapps/companionapp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/streamingtest/Android.bp b/testapps/streamingtest/Android.bp
index bd0a582..8d5cd6c 100644
--- a/testapps/streamingtest/Android.bp
+++ b/testapps/streamingtest/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/transactionalVoipApp/Android.bp b/testapps/transactionalVoipApp/Android.bp
index 68089e2..8bac8f1 100644
--- a/testapps/transactionalVoipApp/Android.bp
+++ b/testapps/transactionalVoipApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 4ca6030..1c27b14 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -24,6 +24,7 @@
android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<!-- TODO: Needed because we call BluetoothAdapter.getDefaultAdapter() statically, and
BluetoothAdapter is a final class. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 148db51..9caf0b5 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -87,6 +87,7 @@
super.setUp();
// this is a mock
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
when(mSubscriptionManager.getActiveSubscriptionInfoList())
.thenReturn(Collections.emptyList());
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
new file mode 100644
index 0000000..08576fc
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.telecom.CallAudioState;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.AudioRoute;
+import com.android.server.telecom.CallAudioRouteController;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.WiredHeadsetManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class CallAudioRouteControllerTest extends TelecomTestCase {
+ private CallAudioRouteController mController;
+ @Mock WiredHeadsetManager mWiredHeadsetManager;
+ @Mock AudioManager mAudioManager;
+ @Mock AudioDeviceInfo mEarpieceDeviceInfo;
+ @Mock CallsManager mCallsManager;
+ private AudioRoute mEarpieceRoute;
+ private AudioRoute mSpeakerRoute;
+ private static final String BT_ADDRESS_1 = "00:00:00:00:00:01";
+ private static final BluetoothDevice BLUETOOTH_DEVICE_1 =
+ BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
+ private static final Set<BluetoothDevice> BLUETOOTH_DEVICES;
+ static {
+ BLUETOOTH_DEVICES = new HashSet<>();
+ BLUETOOTH_DEVICES.add(BLUETOOTH_DEVICE_1);
+ }
+ private static final int TEST_TIMEOUT = 500;
+ AudioRoute.Factory mAudioRouteFactory = new AudioRoute.Factory() {
+ @Override
+ public AudioRoute create(@AudioRoute.AudioRouteType int type, String bluetoothAddress,
+ AudioManager audioManager) {
+ return new AudioRoute(type, bluetoothAddress, null);
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mWiredHeadsetManager.isPluggedIn()).thenReturn(false);
+ when(mEarpieceDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ when(mAudioManager.getDevices(eq(AudioManager.GET_DEVICES_OUTPUTS))).thenReturn(
+ new AudioDeviceInfo[] {
+ mEarpieceDeviceInfo
+ });
+ doNothing().when(mCallsManager).onCallAudioStateChanged(any(CallAudioState.class),
+ any(CallAudioState.class));
+ mController = new CallAudioRouteController(mContext, mCallsManager, mAudioRouteFactory,
+ mWiredHeadsetManager);
+ mController.setAudioRouteFactory(mAudioRouteFactory);
+ mController.setAudioManager(mAudioManager);
+ mEarpieceRoute = new AudioRoute(AudioRoute.TYPE_EARPIECE, null, null);
+ mSpeakerRoute = new AudioRoute(AudioRoute.TYPE_SPEAKER, null, null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mController.getAdapterHandler().getLooper().quit();
+ mController.getAdapterHandler().getLooper().getThread().join();
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testInitializeWithEarpiece() {
+ mController.initialize();
+ assertEquals(mEarpieceRoute, mController.getCurrentRoute());
+ assertEquals(2, mController.getAvailableRoutes().size());
+ assertTrue(mController.getAvailableRoutes().contains(mSpeakerRoute));
+ }
+
+ @SmallTest
+ @Test
+ public void testInitializeWithoutEarpiece() {
+ when(mAudioManager.getDevices(eq(AudioManager.GET_DEVICES_OUTPUTS))).thenReturn(
+ new AudioDeviceInfo[] {});
+
+ mController.initialize();
+ assertEquals(mSpeakerRoute, mController.getCurrentRoute());
+ }
+
+ @SmallTest
+ @Test
+ public void testActivateAndRemoveBluetoothDeviceDuringCall() {
+ doAnswer(invocation -> {
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
+ return true;
+ }).when(mAudioManager).setCommunicationDevice(nullable(AudioDeviceInfo.class));
+
+ mController.initialize();
+ mController.setActive(true);
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
+ verify(mAudioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
+ nullable(AudioDeviceInfo.class));
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(BT_DEVICE_REMOVED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @SmallTest
+ @Test
+ public void testActiveDeactivateBluetoothDevice() {
+ when(mAudioManager.getPreferredDeviceForStrategy(nullable(AudioProductStrategy.class)))
+ .thenReturn(null);
+
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
+ AudioRoute.TYPE_BLUETOOTH_SCO);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @SmallTest
+ @Test
+ public void testSwitchFocusInBluetoothRoute() {
+
+ }
+}