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 android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothHearingAid; 23 import android.bluetooth.BluetoothLeAudio; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.os.Bundle; 30 import android.telecom.Log; 31 import android.telecom.Logging.Session; 32 33 import com.android.internal.os.SomeArgs; 34 35 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON; 36 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST; 37 38 39 public class BluetoothStateReceiver extends BroadcastReceiver { 40 private static final String LOG_TAG = BluetoothStateReceiver.class.getSimpleName(); 41 public static final IntentFilter INTENT_FILTER; 42 static { 43 INTENT_FILTER = new IntentFilter(); 44 INTENT_FILTER.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 45 INTENT_FILTER.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 46 INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 47 INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); 48 INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 49 INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); 50 INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); 51 INTENT_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 52 } 53 54 // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since 55 // other apps could be turning it on and off. We don't want to interfere. 56 private boolean mIsInCall = false; 57 private final BluetoothRouteManager mBluetoothRouteManager; 58 private final BluetoothDeviceManager mBluetoothDeviceManager; 59 onReceive(Context context, Intent intent)60 public void onReceive(Context context, Intent intent) { 61 Log.startSession("BSR.oR"); 62 try { 63 String action = intent.getAction(); 64 switch (action) { 65 case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED: 66 handleAudioStateChanged(intent); 67 break; 68 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: 69 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: 70 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 71 handleConnectionStateChanged(intent); 72 break; 73 case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: 74 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: 75 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: 76 handleActiveDeviceChanged(intent); 77 break; 78 } 79 } finally { 80 Log.endSession(); 81 } 82 } 83 handleAudioStateChanged(Intent intent)84 private void handleAudioStateChanged(Intent intent) { 85 int bluetoothHeadsetAudioState = 86 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 87 BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 88 BluetoothDevice device = 89 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 90 if (device == null) { 91 Log.w(LOG_TAG, "Got null device from broadcast. " + 92 "Ignoring."); 93 return; 94 } 95 96 Log.i(LOG_TAG, "Device %s transitioned to audio state %d", 97 device.getAddress(), bluetoothHeadsetAudioState); 98 Session session = Log.createSubsession(); 99 SomeArgs args = SomeArgs.obtain(); 100 args.arg1 = session; 101 args.arg2 = device.getAddress(); 102 switch (bluetoothHeadsetAudioState) { 103 case BluetoothHeadset.STATE_AUDIO_CONNECTED: 104 if (!mIsInCall) { 105 Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call"); 106 return; 107 } 108 mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args); 109 break; 110 case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: 111 mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args); 112 break; 113 } 114 } 115 handleConnectionStateChanged(Intent intent)116 private void handleConnectionStateChanged(Intent intent) { 117 int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 118 BluetoothHeadset.STATE_DISCONNECTED); 119 BluetoothDevice device = 120 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 121 122 if (device == null) { 123 Log.w(LOG_TAG, "Got null device from broadcast. " + 124 "Ignoring."); 125 return; 126 } 127 128 int deviceType; 129 if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 130 deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO; 131 } else if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 132 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID; 133 } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 134 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET; 135 } else { 136 Log.w(LOG_TAG, "handleConnectionStateChanged: %s invalid device type", device); 137 return; 138 } 139 140 Log.i(LOG_TAG, "%s device %s changed state to %d", 141 BluetoothDeviceManager.getDeviceTypeString(deviceType), 142 device.getAddress(), bluetoothHeadsetState); 143 144 if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) { 145 mBluetoothDeviceManager.onDeviceConnected(device, deviceType); 146 } else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED 147 || bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) { 148 mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType); 149 } 150 } 151 handleActiveDeviceChanged(Intent intent)152 private void handleActiveDeviceChanged(Intent intent) { 153 BluetoothDevice device = 154 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 155 156 int deviceType; 157 if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { 158 deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO; 159 } else if (BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { 160 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID; 161 } else if (BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { 162 deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET; 163 } else { 164 Log.w(LOG_TAG, "handleActiveDeviceChanged: %s invalid device type", device); 165 return; 166 } 167 168 Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device, 169 BluetoothDeviceManager.getDeviceTypeString(deviceType)); 170 171 mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType); 172 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID || 173 deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 174 Session session = Log.createSubsession(); 175 SomeArgs args = SomeArgs.obtain(); 176 args.arg1 = session; 177 if (device == null) { 178 mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args); 179 } else { 180 if (!mIsInCall) { 181 Log.i(LOG_TAG, "Ignoring audio on since we're not in a call"); 182 return; 183 } 184 args.arg2 = device.getAddress(); 185 186 boolean usePreferredAudioProfile = false; 187 BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter(); 188 int preferredDuplexProfile = BluetoothProfile.LE_AUDIO; 189 if (bluetoothAdapter != null) { 190 Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles( 191 device); 192 if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() 193 && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) 194 != 0) { 195 Log.i(this, "Preferred duplex profile for device=" + device + " is " 196 + preferredAudioProfiles.getInt( 197 BluetoothAdapter.AUDIO_MODE_DUPLEX)); 198 usePreferredAudioProfile = true; 199 preferredDuplexProfile = 200 preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); 201 } 202 } 203 204 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 205 /* In Le Audio case, once device got Active, the Telecom needs to make sure it 206 * is set as communication device before we can say that BT_AUDIO_IS_ON 207 */ 208 if ((!usePreferredAudioProfile 209 || preferredDuplexProfile == BluetoothProfile.LE_AUDIO) 210 && !mBluetoothDeviceManager.setLeAudioCommunicationDevice()) { 211 Log.w(LOG_TAG, 212 "Device %s cannot be use as LE audio communication device.", 213 device); 214 return; 215 } 216 } else { 217 /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */ 218 if (!mBluetoothDeviceManager.setHearingAidCommunicationDevice()) { 219 Log.w(LOG_TAG, 220 "Device %s cannot be use as hearing aid communication device.", 221 device); 222 } else { 223 mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args); 224 } 225 } 226 } 227 } 228 } 229 getBluetoothDeviceManager()230 public BluetoothDeviceManager getBluetoothDeviceManager() { 231 return mBluetoothDeviceManager; 232 } 233 BluetoothStateReceiver(BluetoothDeviceManager deviceManager, BluetoothRouteManager routeManager)234 public BluetoothStateReceiver(BluetoothDeviceManager deviceManager, 235 BluetoothRouteManager routeManager) { 236 mBluetoothDeviceManager = deviceManager; 237 mBluetoothRouteManager = routeManager; 238 } 239 setIsInCall(boolean isInCall)240 public void setIsInCall(boolean isInCall) { 241 mIsInCall = isInCall; 242 } 243 } 244