1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.telecom.bluetooth; 18 19 import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE; 20 import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT; 21 import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED; 22 import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED; 23 import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED; 24 import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED; 25 import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED; 26 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON; 27 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST; 28 29 import android.bluetooth.BluetoothAdapter; 30 import android.bluetooth.BluetoothDevice; 31 import android.bluetooth.BluetoothHeadset; 32 import android.bluetooth.BluetoothHearingAid; 33 import android.bluetooth.BluetoothLeAudio; 34 import android.bluetooth.BluetoothProfile; 35 import android.content.BroadcastReceiver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.media.AudioDeviceInfo; 40 import android.os.Bundle; 41 import android.telecom.Log; 42 import android.telecom.Logging.Session; 43 import android.util.Pair; 44 45 import com.android.internal.os.SomeArgs; 46 import com.android.server.telecom.AudioRoute; 47 import com.android.server.telecom.CallAudioCommunicationDeviceTracker; 48 import com.android.server.telecom.CallAudioRouteAdapter; 49 import com.android.server.telecom.CallAudioRouteController; 50 import com.android.server.telecom.flags.FeatureFlags; 51 import com.android.server.telecom.flags.Flags; 52 53 public class BluetoothStateReceiver extends BroadcastReceiver { 54 private static final String LOG_TAG = BluetoothStateReceiver.class.getSimpleName(); 55 public static final IntentFilter INTENT_FILTER; 56 static { 57 INTENT_FILTER = new IntentFilter(); 58 INTENT_FILTER.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 59 INTENT_FILTER.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 60 INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 61 INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); 62 INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 63 INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); 64 INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); 65 INTENT_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 66 } 67 68 // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since 69 // other apps could be turning it on and off. We don't want to interfere. 70 private boolean mIsInCall = false; 71 private final BluetoothRouteManager mBluetoothRouteManager; 72 private final BluetoothDeviceManager mBluetoothDeviceManager; 73 private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker; 74 private FeatureFlags mFeatureFlags; 75 private CallAudioRouteAdapter mCallAudioRouteAdapter; 76 onReceive(Context context, Intent intent)77 public void onReceive(Context context, Intent intent) { 78 Log.startSession("BSR.oR"); 79 try { 80 String action = intent.getAction(); 81 switch (action) { 82 case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED: 83 handleAudioStateChanged(intent); 84 break; 85 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: 86 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: 87 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 88 handleConnectionStateChanged(intent); 89 break; 90 case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: 91 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: 92 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: 93 handleActiveDeviceChanged(intent); 94 break; 95 } 96 } finally { 97 Log.endSession(); 98 } 99 } 100 handleAudioStateChanged(Intent intent)101 private void handleAudioStateChanged(Intent intent) { 102 int bluetoothHeadsetAudioState = 103 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 104 BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 105 BluetoothDevice device = 106 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 107 if (device == null) { 108 Log.w(LOG_TAG, "Got null device from broadcast. " + 109 "Ignoring."); 110 return; 111 } 112 113 Log.i(LOG_TAG, "Device %s transitioned to audio state %d", 114 device.getAddress(), bluetoothHeadsetAudioState); 115 Session session = Log.createSubsession(); 116 SomeArgs args = SomeArgs.obtain(); 117 args.arg1 = session; 118 args.arg2 = device.getAddress(); 119 switch (bluetoothHeadsetAudioState) { 120 case BluetoothHeadset.STATE_AUDIO_CONNECTED: 121 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) { 122 CallAudioRouteController audioRouteController = 123 (CallAudioRouteController) mCallAudioRouteAdapter; 124 audioRouteController.setIsScoAudioConnected(true); 125 if (audioRouteController.isPending()) { 126 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, 127 device); 128 } else { 129 // It's possible that the initial BT connection fails but BT_AUDIO_CONNECTED 130 // is sent later, indicating that SCO audio is on. We should route 131 // appropriately in order for the UI to reflect this state. 132 AudioRoute btRoute = audioRouteController.getBluetoothRoute( 133 AudioRoute.TYPE_BLUETOOTH_SCO, device.getAddress()); 134 if (btRoute != null) { 135 audioRouteController.getPendingAudioRoute().overrideDestRoute(btRoute); 136 audioRouteController.overrideIsPending(true); 137 audioRouteController.getPendingAudioRoute() 138 .setCommunicationDeviceType(AudioRoute.TYPE_BLUETOOTH_SCO); 139 mCallAudioRouteAdapter.sendMessageWithSessionInfo( 140 CallAudioRouteAdapter.EXIT_PENDING_ROUTE); 141 } 142 } 143 } else { 144 if (!mIsInCall) { 145 Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call"); 146 return; 147 } 148 mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args); 149 } 150 break; 151 case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: 152 if (Flags.useRefactoredAudioRouteSwitching()) { 153 CallAudioRouteController audioRouteController = 154 (CallAudioRouteController) mCallAudioRouteAdapter; 155 audioRouteController.setIsScoAudioConnected(false); 156 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, 157 device); 158 } else { 159 mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args); 160 } 161 break; 162 } 163 } 164 handleConnectionStateChanged(Intent intent)165 private void handleConnectionStateChanged(Intent intent) { 166 int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 167 BluetoothHeadset.STATE_DISCONNECTED); 168 BluetoothDevice device = 169 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 170 171 if (device == null) { 172 Log.w(LOG_TAG, "Got null device from broadcast. " + 173 "Ignoring."); 174 return; 175 } 176 177 int deviceType; 178 @AudioRoute.AudioRouteType int audioRouteType; 179 if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 180 deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO; 181 audioRouteType = AudioRoute.TYPE_BLUETOOTH_LE; 182 } else if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 183 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID; 184 audioRouteType = AudioRoute.TYPE_BLUETOOTH_HA; 185 } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 186 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET; 187 audioRouteType = AudioRoute.TYPE_BLUETOOTH_SCO; 188 } else { 189 Log.w(LOG_TAG, "handleConnectionStateChanged: %s invalid device type", device); 190 return; 191 } 192 193 Log.i(LOG_TAG, "%s device %s changed state to %d", 194 BluetoothDeviceManager.getDeviceTypeString(deviceType), 195 device.getAddress(), bluetoothHeadsetState); 196 197 if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) { 198 if (Flags.useRefactoredAudioRouteSwitching()) { 199 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_ADDED, 200 audioRouteType, device); 201 } else { 202 mBluetoothDeviceManager.onDeviceConnected(device, deviceType); 203 } 204 } else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED 205 || bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) { 206 if (Flags.useRefactoredAudioRouteSwitching()) { 207 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_REMOVED, 208 audioRouteType, device); 209 } else { 210 mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType); 211 } 212 } 213 } 214 handleActiveDeviceChanged(Intent intent)215 private void handleActiveDeviceChanged(Intent intent) { 216 BluetoothDevice device = 217 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 218 219 int deviceType; 220 @AudioRoute.AudioRouteType int audioRouteType; 221 if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { 222 deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO; 223 audioRouteType = AudioRoute.TYPE_BLUETOOTH_LE; 224 } else if (BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { 225 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID; 226 audioRouteType = AudioRoute.TYPE_BLUETOOTH_HA; 227 } else if (BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { 228 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET; 229 audioRouteType = AudioRoute.TYPE_BLUETOOTH_SCO; 230 } else { 231 Log.w(LOG_TAG, "handleActiveDeviceChanged: %s invalid device type", device); 232 return; 233 } 234 235 Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device, 236 BluetoothDeviceManager.getDeviceTypeString(deviceType)); 237 238 if (Flags.useRefactoredAudioRouteSwitching()) { 239 CallAudioRouteController audioRouteController = (CallAudioRouteController) 240 mCallAudioRouteAdapter; 241 if (device == null) { 242 audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null)); 243 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE, 244 audioRouteType); 245 } else { 246 audioRouteController.updateActiveBluetoothDevice( 247 new Pair(audioRouteType, device.getAddress())); 248 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, 249 audioRouteType, device.getAddress()); 250 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID 251 || deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 252 if (!mBluetoothDeviceManager.setCommunicationDeviceForAddress( 253 device.getAddress())) { 254 Log.i(this, "handleActiveDeviceChanged: Failed to set " 255 + "communication device for %s. Sending PENDING_ROUTE_FAILED to " 256 + "pending audio route.", device); 257 mCallAudioRouteAdapter.getPendingAudioRoute() 258 .onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED, 259 device.getAddress()), device.getAddress()); 260 } else { 261 // Track the currently set communication device. 262 int routeType = deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO 263 ? AudioRoute.TYPE_BLUETOOTH_LE 264 : AudioRoute.TYPE_BLUETOOTH_HA; 265 mCallAudioRouteAdapter.getPendingAudioRoute() 266 .setCommunicationDeviceType(routeType); 267 } 268 } 269 } 270 } else { 271 mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType); 272 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID || 273 deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 274 Session session = Log.createSubsession(); 275 SomeArgs args = SomeArgs.obtain(); 276 args.arg1 = session; 277 if (device == null) { 278 mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args); 279 } else { 280 if (!mIsInCall) { 281 Log.i(LOG_TAG, "Ignoring audio on since we're not in a call"); 282 return; 283 } 284 args.arg2 = device.getAddress(); 285 286 boolean usePreferredAudioProfile = false; 287 BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager 288 .getBluetoothAdapter(); 289 int preferredDuplexProfile = BluetoothProfile.LE_AUDIO; 290 if (bluetoothAdapter != null) { 291 Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles( 292 device); 293 if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() 294 && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) 295 != 0) { 296 Log.i(this, "Preferred duplex profile for device=" + device + " is " 297 + preferredAudioProfiles.getInt( 298 BluetoothAdapter.AUDIO_MODE_DUPLEX)); 299 usePreferredAudioProfile = true; 300 preferredDuplexProfile = 301 preferredAudioProfiles.getInt( 302 BluetoothAdapter.AUDIO_MODE_DUPLEX); 303 } 304 } 305 306 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 307 /* In Le Audio case, once device got Active, the Telecom needs to make sure 308 * it is set as communication device before we can say that BT_AUDIO_IS_ON 309 */ 310 boolean isLeAudioSetForCommunication = 311 mFeatureFlags.callAudioCommunicationDeviceRefactor() 312 ? mCommunicationDeviceTracker.setCommunicationDevice( 313 AudioDeviceInfo.TYPE_BLE_HEADSET, device) 314 : mBluetoothDeviceManager.setLeAudioCommunicationDevice(); 315 if ((!usePreferredAudioProfile 316 || preferredDuplexProfile == BluetoothProfile.LE_AUDIO) 317 && !isLeAudioSetForCommunication) { 318 Log.w(LOG_TAG, 319 "Device %s cannot be use as LE audio communication device.", 320 device); 321 } 322 } else { 323 boolean isHearingAidSetForCommunication = 324 mFeatureFlags.callAudioCommunicationDeviceRefactor() 325 ? mCommunicationDeviceTracker.setCommunicationDevice( 326 AudioDeviceInfo.TYPE_HEARING_AID, null) 327 : mBluetoothDeviceManager 328 .setHearingAidCommunicationDevice(); 329 /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */ 330 if (!isHearingAidSetForCommunication) { 331 Log.w(LOG_TAG, 332 "Device %s cannot be use as hearing aid communication device.", 333 device); 334 } else { 335 mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args); 336 } 337 } 338 } 339 } 340 } 341 } 342 getBluetoothDeviceManager()343 public BluetoothDeviceManager getBluetoothDeviceManager() { 344 return mBluetoothDeviceManager; 345 } 346 BluetoothStateReceiver(BluetoothDeviceManager deviceManager, BluetoothRouteManager routeManager, CallAudioCommunicationDeviceTracker communicationDeviceTracker, FeatureFlags featureFlags)347 public BluetoothStateReceiver(BluetoothDeviceManager deviceManager, 348 BluetoothRouteManager routeManager, 349 CallAudioCommunicationDeviceTracker communicationDeviceTracker, 350 FeatureFlags featureFlags) { 351 mBluetoothDeviceManager = deviceManager; 352 mBluetoothRouteManager = routeManager; 353 mCommunicationDeviceTracker = communicationDeviceTracker; 354 mFeatureFlags = featureFlags; 355 } 356 setIsInCall(boolean isInCall)357 public void setIsInCall(boolean isInCall) { 358 mIsInCall = isInCall; 359 } 360 setCallAudioRouteAdapter(CallAudioRouteAdapter adapter)361 public void setCallAudioRouteAdapter(CallAudioRouteAdapter adapter) { 362 mCallAudioRouteAdapter = adapter; 363 } 364 } 365