Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 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 | package com.android.server.audio; |
| 17 | |
| 18 | import android.annotation.NonNull; |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 19 | import android.annotation.Nullable; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 20 | import android.bluetooth.BluetoothA2dp; |
| 21 | import android.bluetooth.BluetoothAdapter; |
| 22 | import android.bluetooth.BluetoothClass; |
| 23 | import android.bluetooth.BluetoothCodecConfig; |
| 24 | import android.bluetooth.BluetoothCodecStatus; |
| 25 | import android.bluetooth.BluetoothDevice; |
| 26 | import android.bluetooth.BluetoothHeadset; |
| 27 | import android.bluetooth.BluetoothHearingAid; |
| 28 | import android.bluetooth.BluetoothProfile; |
| 29 | import android.content.Intent; |
| 30 | import android.media.AudioManager; |
| 31 | import android.media.AudioSystem; |
| 32 | import android.os.Binder; |
| 33 | import android.os.IBinder; |
| 34 | import android.os.RemoteException; |
| 35 | import android.os.UserHandle; |
| 36 | import android.provider.Settings; |
| 37 | import android.util.Log; |
| 38 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 39 | import java.util.ArrayList; |
| 40 | import java.util.List; |
| 41 | import java.util.NoSuchElementException; |
| 42 | import java.util.Objects; |
| 43 | |
| 44 | /** |
| 45 | * @hide |
| 46 | * Class to encapsulate all communication with Bluetooth services |
| 47 | */ |
| 48 | public class BtHelper { |
| 49 | |
| 50 | private static final String TAG = "AS.BtHelper"; |
| 51 | |
| 52 | private final @NonNull AudioDeviceBroker mDeviceBroker; |
| 53 | |
| 54 | BtHelper(@NonNull AudioDeviceBroker broker) { |
| 55 | mDeviceBroker = broker; |
| 56 | } |
| 57 | |
| 58 | // List of clients having issued a SCO start request |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 59 | private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 60 | |
| 61 | // BluetoothHeadset API to control SCO connection |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 62 | private @Nullable BluetoothHeadset mBluetoothHeadset; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 63 | |
| 64 | // Bluetooth headset device |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 65 | private @Nullable BluetoothDevice mBluetoothHeadsetDevice; |
| 66 | |
| 67 | private @Nullable BluetoothHearingAid mHearingAid; |
| 68 | |
| 69 | // Reference to BluetoothA2dp to query for AbsoluteVolume. |
| 70 | private @Nullable BluetoothA2dp mA2dp; |
| 71 | |
| 72 | // If absolute volume is supported in AVRCP device |
| 73 | private boolean mAvrcpAbsVolSupported = false; |
| 74 | |
| 75 | // Current connection state indicated by bluetooth headset |
| 76 | private int mScoConnectionState; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 77 | |
| 78 | // Indicate if SCO audio connection is currently active and if the initiator is |
| 79 | // audio service (internal) or bluetooth headset (external) |
| 80 | private int mScoAudioState; |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 81 | |
| 82 | // Indicates the mode used for SCO audio connection. The mode is virtual call if the request |
| 83 | // originated from an app targeting an API version before JB MR2 and raw audio after that. |
| 84 | private int mScoAudioMode; |
| 85 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 86 | // SCO audio state is not active |
| 87 | private static final int SCO_STATE_INACTIVE = 0; |
| 88 | // SCO audio activation request waiting for headset service to connect |
| 89 | private static final int SCO_STATE_ACTIVATE_REQ = 1; |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 90 | // SCO audio state is active due to an action in BT handsfree (either voice recognition or |
| 91 | // in call audio) |
| 92 | private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 93 | // SCO audio state is active or starting due to a request from AudioManager API |
| 94 | private static final int SCO_STATE_ACTIVE_INTERNAL = 3; |
| 95 | // SCO audio deactivation request waiting for headset service to connect |
| 96 | private static final int SCO_STATE_DEACTIVATE_REQ = 4; |
| 97 | // SCO audio deactivation in progress, waiting for Bluetooth audio intent |
| 98 | private static final int SCO_STATE_DEACTIVATING = 5; |
| 99 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 100 | // SCO audio mode is undefined |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 101 | /*package*/ static final int SCO_MODE_UNDEFINED = -1; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 102 | // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) |
| 103 | /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; |
| 104 | // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) |
| 105 | private static final int SCO_MODE_RAW = 1; |
| 106 | // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) |
| 107 | private static final int SCO_MODE_VR = 2; |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 108 | // max valid SCO audio mode values |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 109 | private static final int SCO_MODE_MAX = 2; |
| 110 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 111 | private static final int BT_HEARING_AID_GAIN_MIN = -128; |
| 112 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 113 | //---------------------------------------------------------------------- |
| 114 | /*package*/ static class BluetoothA2dpDeviceInfo { |
| 115 | private final @NonNull BluetoothDevice mBtDevice; |
| 116 | private final int mVolume; |
| 117 | private final int mCodec; |
| 118 | |
| 119 | BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) { |
| 120 | this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); |
| 121 | } |
| 122 | |
| 123 | BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) { |
| 124 | mBtDevice = btDevice; |
| 125 | mVolume = volume; |
| 126 | mCodec = codec; |
| 127 | } |
| 128 | |
| 129 | public @NonNull BluetoothDevice getBtDevice() { |
| 130 | return mBtDevice; |
| 131 | } |
| 132 | |
| 133 | public int getVolume() { |
| 134 | return mVolume; |
| 135 | } |
| 136 | |
| 137 | public int getCodec() { |
| 138 | return mCodec; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | //---------------------------------------------------------------------- |
| 143 | // Interface for AudioDeviceBroker |
| 144 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 145 | /*package*/ synchronized void onSystemReady() { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 146 | mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; |
| 147 | resetBluetoothSco(); |
| 148 | getBluetoothHeadset(); |
| 149 | |
| 150 | //FIXME: this is to maintain compatibility with deprecated intent |
| 151 | // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. |
| 152 | Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); |
| 153 | newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, |
| 154 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 155 | sendStickyBroadcastToAll(newIntent); |
| 156 | |
| 157 | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| 158 | if (adapter != null) { |
| 159 | adapter.getProfileProxy(mDeviceBroker.getContext(), |
| 160 | mBluetoothProfileServiceListener, BluetoothProfile.A2DP); |
| 161 | adapter.getProfileProxy(mDeviceBroker.getContext(), |
| 162 | mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID); |
| 163 | } |
| 164 | } |
| 165 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 166 | /*package*/ synchronized void onAudioServerDiedRestoreA2dp() { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 167 | final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() |
| 168 | ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; |
| 169 | mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); |
| 170 | } |
| 171 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 172 | /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 173 | return (mA2dp != null && mAvrcpAbsVolSupported); |
| 174 | } |
| 175 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 176 | /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 177 | mAvrcpAbsVolSupported = supported; |
| 178 | } |
| 179 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 180 | /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) { |
| 181 | if (mA2dp == null) { |
| 182 | if (AudioService.DEBUG_VOL) { |
| 183 | Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp"); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 184 | return; |
| 185 | } |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 186 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 187 | if (!mAvrcpAbsVolSupported) { |
| 188 | return; |
| 189 | } |
| 190 | if (AudioService.DEBUG_VOL) { |
| 191 | Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); |
| 192 | } |
| 193 | AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( |
Jean-Michel Trivi | 734819a | 2019-02-04 15:44:28 -0800 | [diff] [blame] | 194 | AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); |
| 195 | mA2dp.setAvrcpAbsoluteVolume(index); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 196 | } |
| 197 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 198 | /*package*/ synchronized int getA2dpCodec(@NonNull BluetoothDevice device) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 199 | if (mA2dp == null) { |
| 200 | return AudioSystem.AUDIO_FORMAT_DEFAULT; |
| 201 | } |
| 202 | final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); |
| 203 | if (btCodecStatus == null) { |
| 204 | return AudioSystem.AUDIO_FORMAT_DEFAULT; |
| 205 | } |
| 206 | final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); |
| 207 | if (btCodecConfig == null) { |
| 208 | return AudioSystem.AUDIO_FORMAT_DEFAULT; |
| 209 | } |
| 210 | return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); |
| 211 | } |
| 212 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 213 | /*package*/ synchronized void receiveBtEvent(Intent intent) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 214 | final String action = intent.getAction(); |
| 215 | if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { |
| 216 | BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| 217 | setBtScoActiveDevice(btDevice); |
| 218 | } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { |
| 219 | boolean broadcast = false; |
| 220 | int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 221 | int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); |
| 222 | // broadcast intent if the connection was initated by AudioService |
| 223 | if (!mScoClients.isEmpty() |
| 224 | && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL |
| 225 | || mScoAudioState == SCO_STATE_ACTIVATE_REQ |
| 226 | || mScoAudioState == SCO_STATE_DEACTIVATE_REQ |
| 227 | || mScoAudioState == SCO_STATE_DEACTIVATING)) { |
| 228 | broadcast = true; |
| 229 | } |
| 230 | switch (btState) { |
| 231 | case BluetoothHeadset.STATE_AUDIO_CONNECTED: |
| 232 | scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; |
| 233 | if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL |
| 234 | && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { |
| 235 | mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; |
| 236 | } |
| 237 | mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); |
| 238 | break; |
| 239 | case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: |
| 240 | mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent"); |
| 241 | scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; |
| 242 | // startBluetoothSco called after stopBluetoothSco |
| 243 | if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { |
| 244 | if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null |
| 245 | && connectBluetoothScoAudioHelper(mBluetoothHeadset, |
| 246 | mBluetoothHeadsetDevice, mScoAudioMode)) { |
| 247 | mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; |
| 248 | broadcast = false; |
| 249 | break; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 250 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 251 | } |
| 252 | // Tear down SCO if disconnected from external |
| 253 | clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); |
| 254 | mScoAudioState = SCO_STATE_INACTIVE; |
| 255 | break; |
| 256 | case BluetoothHeadset.STATE_AUDIO_CONNECTING: |
| 257 | if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL |
| 258 | && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { |
| 259 | mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; |
| 260 | } |
| 261 | break; |
| 262 | default: |
| 263 | // do not broadcast CONNECTING or invalid state |
| 264 | broadcast = false; |
| 265 | break; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 266 | } |
| 267 | if (broadcast) { |
| 268 | broadcastScoConnectionState(scoAudioState); |
| 269 | //FIXME: this is to maintain compatibility with deprecated intent |
| 270 | // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. |
| 271 | Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); |
| 272 | newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); |
| 273 | sendStickyBroadcastToAll(newIntent); |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * |
| 280 | * @return false if SCO isn't connected |
| 281 | */ |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 282 | /*package*/ synchronized boolean isBluetoothScoOn() { |
| 283 | if ((mBluetoothHeadset != null) |
| 284 | && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) |
| 285 | != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { |
| 286 | Log.w(TAG, "isBluetoothScoOn(true) returning false because " |
| 287 | + mBluetoothHeadsetDevice + " is not in audio connected mode"); |
| 288 | return false; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 289 | } |
| 290 | return true; |
| 291 | } |
| 292 | |
| 293 | /** |
| 294 | * Disconnect all SCO connections started by {@link AudioManager} except those started by |
| 295 | * {@param exceptPid} |
| 296 | * |
| 297 | * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept |
| 298 | */ |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 299 | /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) { |
| 300 | checkScoAudioState(); |
| 301 | if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { |
| 302 | return; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 303 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 304 | clearAllScoClients(exceptPid, true); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 305 | } |
| 306 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 307 | /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode, |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 308 | @NonNull String eventSource) { |
| 309 | ScoClient client = getScoClient(cb, true); |
| 310 | // The calling identity must be cleared before calling ScoClient.incCount(). |
| 311 | // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs |
| 312 | // and this must be done on behalf of system server to make sure permissions are granted. |
| 313 | // The caller identity must be cleared after getScoClient() because it is needed if a new |
| 314 | // client is created. |
| 315 | final long ident = Binder.clearCallingIdentity(); |
| 316 | try { |
| 317 | eventSource += " client count before=" + client.getCount(); |
| 318 | AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); |
| 319 | client.incCount(scoAudioMode); |
| 320 | } catch (NullPointerException e) { |
| 321 | Log.e(TAG, "Null ScoClient", e); |
| 322 | } |
| 323 | Binder.restoreCallingIdentity(ident); |
| 324 | } |
| 325 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 326 | /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb, |
| 327 | @NonNull String eventSource) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 328 | ScoClient client = getScoClient(cb, false); |
| 329 | // The calling identity must be cleared before calling ScoClient.decCount(). |
| 330 | // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs |
| 331 | // and this must be done on behalf of system server to make sure permissions are granted. |
| 332 | final long ident = Binder.clearCallingIdentity(); |
| 333 | if (client != null) { |
| 334 | eventSource += " client count before=" + client.getCount(); |
| 335 | AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); |
| 336 | client.decCount(); |
| 337 | } |
| 338 | Binder.restoreCallingIdentity(ident); |
| 339 | } |
| 340 | |
| 341 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 342 | /*package*/ synchronized void setHearingAidVolume(int index, int streamType) { |
| 343 | if (mHearingAid == null) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 344 | if (AudioService.DEBUG_VOL) { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 345 | Log.i(TAG, "setHearingAidVolume: null mHearingAid"); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 346 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 347 | return; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 348 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 349 | //hearing aid expect volume value in range -128dB to 0dB |
| 350 | int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, |
| 351 | AudioSystem.DEVICE_OUT_HEARING_AID); |
| 352 | if (gainDB < BT_HEARING_AID_GAIN_MIN) { |
| 353 | gainDB = BT_HEARING_AID_GAIN_MIN; |
| 354 | } |
| 355 | if (AudioService.DEBUG_VOL) { |
| 356 | Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" |
| 357 | + index + " gain=" + gainDB); |
| 358 | } |
| 359 | AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( |
| 360 | AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); |
| 361 | mHearingAid.setVolume(gainDB); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 362 | } |
| 363 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 364 | /*package*/ synchronized void onBroadcastScoConnectionState(int state) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 365 | if (state == mScoConnectionState) { |
| 366 | return; |
| 367 | } |
| 368 | Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); |
| 369 | newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); |
| 370 | newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, |
| 371 | mScoConnectionState); |
| 372 | sendStickyBroadcastToAll(newIntent); |
| 373 | mScoConnectionState = state; |
| 374 | } |
| 375 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 376 | /*package*/ synchronized void disconnectAllBluetoothProfiles() { |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 377 | mDeviceBroker.postDisconnectA2dp(); |
| 378 | mDeviceBroker.postDisconnectA2dpSink(); |
| 379 | mDeviceBroker.postDisconnectHeadset(); |
| 380 | mDeviceBroker.postDisconnectHearingAid(); |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 381 | } |
| 382 | |
| 383 | /*package*/ synchronized void resetBluetoothSco() { |
| 384 | clearAllScoClients(0, false); |
| 385 | mScoAudioState = SCO_STATE_INACTIVE; |
| 386 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 387 | AudioSystem.setParameters("A2dpSuspended=false"); |
| 388 | mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); |
| 389 | } |
| 390 | |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 391 | /*package*/ synchronized void disconnectHeadset() { |
| 392 | setBtScoActiveDevice(null); |
| 393 | mBluetoothHeadset = null; |
| 394 | } |
| 395 | |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 396 | /*package*/ synchronized void onA2dpProfileConnected(BluetoothA2dp a2dp) { |
| 397 | mA2dp = a2dp; |
| 398 | final List<BluetoothDevice> deviceList = mA2dp.getConnectedDevices(); |
| 399 | if (deviceList.isEmpty()) { |
| 400 | return; |
| 401 | } |
| 402 | final BluetoothDevice btDevice = deviceList.get(0); |
| 403 | final @BluetoothProfile.BtProfileState int state = mA2dp.getConnectionState(btDevice); |
| 404 | mDeviceBroker.handleSetA2dpSinkConnectionState( |
| 405 | state, new BluetoothA2dpDeviceInfo(btDevice)); |
| 406 | } |
| 407 | |
| 408 | /*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) { |
| 409 | final List<BluetoothDevice> deviceList = profile.getConnectedDevices(); |
| 410 | if (deviceList.isEmpty()) { |
| 411 | return; |
| 412 | } |
| 413 | final BluetoothDevice btDevice = deviceList.get(0); |
| 414 | final @BluetoothProfile.BtProfileState int state = |
| 415 | profile.getConnectionState(btDevice); |
| 416 | mDeviceBroker.postSetA2dpSourceConnectionState( |
| 417 | state, new BluetoothA2dpDeviceInfo(btDevice)); |
| 418 | } |
| 419 | |
| 420 | /*package*/ synchronized void onHearingAidProfileConnected(BluetoothHearingAid hearingAid) { |
| 421 | mHearingAid = hearingAid; |
| 422 | final List<BluetoothDevice> deviceList = mHearingAid.getConnectedDevices(); |
| 423 | if (deviceList.isEmpty()) { |
| 424 | return; |
| 425 | } |
| 426 | final BluetoothDevice btDevice = deviceList.get(0); |
| 427 | final @BluetoothProfile.BtProfileState int state = |
| 428 | mHearingAid.getConnectionState(btDevice); |
Jean-Michel Trivi | fc86cfa | 2019-03-01 10:15:47 -0800 | [diff] [blame] | 429 | mDeviceBroker.postBluetoothHearingAidDeviceConnectionState( |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 430 | btDevice, state, |
| 431 | /*suppressNoisyIntent*/ false, |
| 432 | /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE, |
| 433 | /*eventSource*/ "mBluetoothProfileServiceListener"); |
| 434 | } |
| 435 | |
| 436 | /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) { |
| 437 | // Discard timeout message |
| 438 | mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); |
| 439 | mBluetoothHeadset = headset; |
| 440 | setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); |
| 441 | // Refresh SCO audio state |
| 442 | checkScoAudioState(); |
| 443 | if (mScoAudioState != SCO_STATE_ACTIVATE_REQ |
| 444 | && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { |
| 445 | return; |
| 446 | } |
| 447 | boolean status = false; |
| 448 | if (mBluetoothHeadsetDevice != null) { |
| 449 | switch (mScoAudioState) { |
| 450 | case SCO_STATE_ACTIVATE_REQ: |
| 451 | status = connectBluetoothScoAudioHelper( |
| 452 | mBluetoothHeadset, |
| 453 | mBluetoothHeadsetDevice, mScoAudioMode); |
| 454 | if (status) { |
| 455 | mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; |
| 456 | } |
| 457 | break; |
| 458 | case SCO_STATE_DEACTIVATE_REQ: |
| 459 | status = disconnectBluetoothScoAudioHelper( |
| 460 | mBluetoothHeadset, |
| 461 | mBluetoothHeadsetDevice, mScoAudioMode); |
| 462 | if (status) { |
| 463 | mScoAudioState = SCO_STATE_DEACTIVATING; |
| 464 | } |
| 465 | break; |
| 466 | } |
| 467 | } |
| 468 | if (!status) { |
| 469 | mScoAudioState = SCO_STATE_INACTIVE; |
| 470 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 471 | } |
| 472 | } |
| 473 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 474 | //---------------------------------------------------------------------- |
| 475 | private void broadcastScoConnectionState(int state) { |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 476 | mDeviceBroker.postBroadcastScoConnectionState(state); |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 477 | } |
| 478 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 479 | private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { |
| 480 | if (btDevice == null) { |
| 481 | return true; |
| 482 | } |
| 483 | String address = btDevice.getAddress(); |
| 484 | BluetoothClass btClass = btDevice.getBluetoothClass(); |
| 485 | int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| 486 | int[] outDeviceTypes = { |
| 487 | AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, |
| 488 | AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, |
| 489 | AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
| 490 | }; |
| 491 | if (btClass != null) { |
| 492 | switch (btClass.getDeviceClass()) { |
| 493 | case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: |
| 494 | case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: |
| 495 | outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; |
| 496 | break; |
| 497 | case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: |
| 498 | outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; |
| 499 | break; |
| 500 | } |
| 501 | } |
| 502 | if (!BluetoothAdapter.checkBluetoothAddress(address)) { |
| 503 | address = ""; |
| 504 | } |
| 505 | String btDeviceName = btDevice.getName(); |
| 506 | boolean result = false; |
| 507 | if (isActive) { |
| 508 | result |= mDeviceBroker.handleDeviceConnection( |
| 509 | isActive, outDeviceTypes[0], address, btDeviceName); |
| 510 | } else { |
| 511 | for (int outDeviceType : outDeviceTypes) { |
| 512 | result |= mDeviceBroker.handleDeviceConnection( |
| 513 | isActive, outDeviceType, address, btDeviceName); |
| 514 | } |
| 515 | } |
| 516 | // handleDeviceConnection() && result to make sure the method get executed |
| 517 | result = mDeviceBroker.handleDeviceConnection( |
| 518 | isActive, inDevice, address, btDeviceName) && result; |
| 519 | return result; |
| 520 | } |
| 521 | |
| 522 | private void setBtScoActiveDevice(BluetoothDevice btDevice) { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 523 | Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); |
| 524 | final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; |
| 525 | if (Objects.equals(btDevice, previousActiveDevice)) { |
| 526 | return; |
| 527 | } |
| 528 | if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { |
| 529 | Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " |
| 530 | + previousActiveDevice); |
| 531 | } |
| 532 | if (!handleBtScoActiveDeviceChange(btDevice, true)) { |
| 533 | Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); |
| 534 | // set mBluetoothHeadsetDevice to null when failing to add new device |
| 535 | btDevice = null; |
| 536 | } |
| 537 | mBluetoothHeadsetDevice = btDevice; |
| 538 | if (mBluetoothHeadsetDevice == null) { |
| 539 | resetBluetoothSco(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 540 | } |
| 541 | } |
| 542 | |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 543 | // NOTE this listener is NOT called from AudioDeviceBroker event thread, only call async |
| 544 | // methods inside listener. |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 545 | private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = |
| 546 | new BluetoothProfile.ServiceListener() { |
| 547 | public void onServiceConnected(int profile, BluetoothProfile proxy) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 548 | switch(profile) { |
| 549 | case BluetoothProfile.A2DP: |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 550 | AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( |
| 551 | "BT profile service: connecting A2DP profile")); |
| 552 | mDeviceBroker.postBtA2dpProfileConnected((BluetoothA2dp) proxy); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 553 | break; |
| 554 | |
| 555 | case BluetoothProfile.A2DP_SINK: |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 556 | AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( |
| 557 | "BT profile service: connecting A2DP_SINK profile")); |
| 558 | mDeviceBroker.postBtA2dpSinkProfileConnected(proxy); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 559 | break; |
| 560 | |
| 561 | case BluetoothProfile.HEADSET: |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 562 | AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( |
| 563 | "BT profile service: connecting HEADSET profile")); |
| 564 | mDeviceBroker.postBtHeasetProfileConnected((BluetoothHeadset) proxy); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 565 | break; |
| 566 | |
| 567 | case BluetoothProfile.HEARING_AID: |
Jean-Michel Trivi | 43ff4f3 | 2019-02-07 12:08:39 -0800 | [diff] [blame] | 568 | AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( |
| 569 | "BT profile service: connecting HEARING_AID profile")); |
| 570 | mDeviceBroker.postBtHearingAidProfileConnected( |
| 571 | (BluetoothHearingAid) proxy); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 572 | break; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 573 | default: |
| 574 | break; |
| 575 | } |
| 576 | } |
| 577 | public void onServiceDisconnected(int profile) { |
| 578 | |
| 579 | switch (profile) { |
| 580 | case BluetoothProfile.A2DP: |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 581 | mDeviceBroker.postDisconnectA2dp(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 582 | break; |
| 583 | |
| 584 | case BluetoothProfile.A2DP_SINK: |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 585 | mDeviceBroker.postDisconnectA2dpSink(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 586 | break; |
| 587 | |
| 588 | case BluetoothProfile.HEADSET: |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 589 | mDeviceBroker.postDisconnectHeadset(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 590 | break; |
| 591 | |
| 592 | case BluetoothProfile.HEARING_AID: |
Jean-Michel Trivi | b946515 | 2019-02-06 15:20:19 -0800 | [diff] [blame] | 593 | mDeviceBroker.postDisconnectHearingAid(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 594 | break; |
| 595 | |
| 596 | default: |
| 597 | break; |
| 598 | } |
| 599 | } |
| 600 | }; |
| 601 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 602 | //---------------------------------------------------------------------- |
| 603 | private class ScoClient implements IBinder.DeathRecipient { |
| 604 | private IBinder mCb; // To be notified of client's death |
| 605 | private int mCreatorPid; |
| 606 | private int mStartcount; // number of SCO connections started by this client |
| 607 | |
| 608 | ScoClient(IBinder cb) { |
| 609 | mCb = cb; |
| 610 | mCreatorPid = Binder.getCallingPid(); |
| 611 | mStartcount = 0; |
| 612 | } |
| 613 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 614 | @Override |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 615 | public void binderDied() { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 616 | // this is the only place the implementation of ScoClient needs to be synchronized |
| 617 | // on the instance, as all other methods are directly or indirectly called from |
| 618 | // package-private methods, which are synchronized |
| 619 | synchronized (BtHelper.this) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 620 | Log.w(TAG, "SCO client died"); |
| 621 | int index = mScoClients.indexOf(this); |
| 622 | if (index < 0) { |
| 623 | Log.w(TAG, "unregistered SCO client died"); |
| 624 | } else { |
| 625 | clearCount(true); |
| 626 | mScoClients.remove(this); |
| 627 | } |
| 628 | } |
| 629 | } |
| 630 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 631 | void incCount(int scoAudioMode) { |
| 632 | requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); |
| 633 | if (mStartcount == 0) { |
| 634 | try { |
| 635 | mCb.linkToDeath(this, 0); |
| 636 | } catch (RemoteException e) { |
| 637 | // client has already died! |
| 638 | Log.w(TAG, "ScoClient incCount() could not link to " |
| 639 | + mCb + " binder death"); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 640 | } |
| 641 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 642 | mStartcount++; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 643 | } |
| 644 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 645 | void decCount() { |
| 646 | if (mStartcount == 0) { |
| 647 | Log.w(TAG, "ScoClient.decCount() already 0"); |
| 648 | } else { |
| 649 | mStartcount--; |
| 650 | if (mStartcount == 0) { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 651 | try { |
| 652 | mCb.unlinkToDeath(this, 0); |
| 653 | } catch (NoSuchElementException e) { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 654 | Log.w(TAG, "decCount() going to 0 but not registered to binder"); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 655 | } |
| 656 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 657 | requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 658 | } |
| 659 | } |
| 660 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 661 | void clearCount(boolean stopSco) { |
| 662 | if (mStartcount != 0) { |
| 663 | try { |
| 664 | mCb.unlinkToDeath(this, 0); |
| 665 | } catch (NoSuchElementException e) { |
| 666 | Log.w(TAG, "clearCount() mStartcount: " |
| 667 | + mStartcount + " != 0 but not registered to binder"); |
| 668 | } |
| 669 | } |
| 670 | mStartcount = 0; |
| 671 | if (stopSco) { |
| 672 | requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | int getCount() { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 677 | return mStartcount; |
| 678 | } |
| 679 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 680 | IBinder getBinder() { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 681 | return mCb; |
| 682 | } |
| 683 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 684 | int getPid() { |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 685 | return mCreatorPid; |
| 686 | } |
| 687 | |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 688 | private int totalCount() { |
| 689 | int count = 0; |
| 690 | for (ScoClient mScoClient : mScoClients) { |
| 691 | count += mScoClient.getCount(); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 692 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 693 | return count; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 694 | } |
| 695 | |
| 696 | private void requestScoState(int state, int scoAudioMode) { |
| 697 | checkScoAudioState(); |
| 698 | int clientCount = totalCount(); |
| 699 | if (clientCount != 0) { |
| 700 | Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode |
| 701 | + ", clientCount=" + clientCount); |
| 702 | return; |
| 703 | } |
| 704 | if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { |
| 705 | // Make sure that the state transitions to CONNECTING even if we cannot initiate |
| 706 | // the connection. |
| 707 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); |
| 708 | // Accept SCO audio activation only in NORMAL audio mode or if the mode is |
| 709 | // currently controlled by the same client process. |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 710 | // TODO do not sync that way, see b/123769055 |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 711 | synchronized (mDeviceBroker.mSetModeLock) { |
| 712 | int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty() |
| 713 | ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid(); |
| 714 | if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { |
| 715 | Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " |
| 716 | + modeOwnerPid + " != creatorPid " + mCreatorPid); |
| 717 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 718 | return; |
| 719 | } |
| 720 | switch (mScoAudioState) { |
| 721 | case SCO_STATE_INACTIVE: |
| 722 | mScoAudioMode = scoAudioMode; |
| 723 | if (scoAudioMode == SCO_MODE_UNDEFINED) { |
| 724 | mScoAudioMode = SCO_MODE_VIRTUAL_CALL; |
| 725 | if (mBluetoothHeadsetDevice != null) { |
| 726 | mScoAudioMode = Settings.Global.getInt( |
| 727 | mDeviceBroker.getContentResolver(), |
| 728 | "bluetooth_sco_channel_" |
| 729 | + mBluetoothHeadsetDevice.getAddress(), |
| 730 | SCO_MODE_VIRTUAL_CALL); |
| 731 | if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { |
| 732 | mScoAudioMode = SCO_MODE_VIRTUAL_CALL; |
| 733 | } |
| 734 | } |
| 735 | } |
| 736 | if (mBluetoothHeadset == null) { |
| 737 | if (getBluetoothHeadset()) { |
| 738 | mScoAudioState = SCO_STATE_ACTIVATE_REQ; |
| 739 | } else { |
| 740 | Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" |
| 741 | + " connection, mScoAudioMode=" + mScoAudioMode); |
| 742 | broadcastScoConnectionState( |
| 743 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 744 | } |
| 745 | break; |
| 746 | } |
| 747 | if (mBluetoothHeadsetDevice == null) { |
| 748 | Log.w(TAG, "requestScoState: no active device while connecting," |
| 749 | + " mScoAudioMode=" + mScoAudioMode); |
| 750 | broadcastScoConnectionState( |
| 751 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 752 | break; |
| 753 | } |
| 754 | if (connectBluetoothScoAudioHelper(mBluetoothHeadset, |
| 755 | mBluetoothHeadsetDevice, mScoAudioMode)) { |
| 756 | mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; |
| 757 | } else { |
| 758 | Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice |
| 759 | + " failed, mScoAudioMode=" + mScoAudioMode); |
| 760 | broadcastScoConnectionState( |
| 761 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 762 | } |
| 763 | break; |
| 764 | case SCO_STATE_DEACTIVATING: |
| 765 | mScoAudioState = SCO_STATE_ACTIVATE_REQ; |
| 766 | break; |
| 767 | case SCO_STATE_DEACTIVATE_REQ: |
| 768 | mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; |
| 769 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); |
| 770 | break; |
| 771 | default: |
| 772 | Log.w(TAG, "requestScoState: failed to connect in state " |
| 773 | + mScoAudioState + ", scoAudioMode=" + scoAudioMode); |
| 774 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 775 | break; |
| 776 | |
| 777 | } |
| 778 | } |
| 779 | } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { |
| 780 | switch (mScoAudioState) { |
| 781 | case SCO_STATE_ACTIVE_INTERNAL: |
| 782 | if (mBluetoothHeadset == null) { |
| 783 | if (getBluetoothHeadset()) { |
| 784 | mScoAudioState = SCO_STATE_DEACTIVATE_REQ; |
| 785 | } else { |
| 786 | Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" |
| 787 | + " disconnection, mScoAudioMode=" + mScoAudioMode); |
| 788 | mScoAudioState = SCO_STATE_INACTIVE; |
| 789 | broadcastScoConnectionState( |
| 790 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 791 | } |
| 792 | break; |
| 793 | } |
| 794 | if (mBluetoothHeadsetDevice == null) { |
| 795 | mScoAudioState = SCO_STATE_INACTIVE; |
| 796 | broadcastScoConnectionState( |
| 797 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 798 | break; |
| 799 | } |
| 800 | if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, |
| 801 | mBluetoothHeadsetDevice, mScoAudioMode)) { |
| 802 | mScoAudioState = SCO_STATE_DEACTIVATING; |
| 803 | } else { |
| 804 | mScoAudioState = SCO_STATE_INACTIVE; |
| 805 | broadcastScoConnectionState( |
| 806 | AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 807 | } |
| 808 | break; |
| 809 | case SCO_STATE_ACTIVATE_REQ: |
| 810 | mScoAudioState = SCO_STATE_INACTIVE; |
| 811 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 812 | break; |
| 813 | default: |
| 814 | Log.w(TAG, "requestScoState: failed to disconnect in state " |
| 815 | + mScoAudioState + ", scoAudioMode=" + scoAudioMode); |
| 816 | broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| 817 | break; |
| 818 | } |
| 819 | } |
| 820 | } |
| 821 | } |
| 822 | |
| 823 | //----------------------------------------------------- |
| 824 | // Utilities |
| 825 | private void sendStickyBroadcastToAll(Intent intent) { |
| 826 | intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| 827 | final long ident = Binder.clearCallingIdentity(); |
| 828 | try { |
| 829 | mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); |
| 830 | } finally { |
| 831 | Binder.restoreCallingIdentity(ident); |
| 832 | } |
| 833 | } |
| 834 | |
| 835 | private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, |
| 836 | BluetoothDevice device, int scoAudioMode) { |
| 837 | switch (scoAudioMode) { |
| 838 | case SCO_MODE_RAW: |
| 839 | return bluetoothHeadset.disconnectAudio(); |
| 840 | case SCO_MODE_VIRTUAL_CALL: |
| 841 | return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); |
| 842 | case SCO_MODE_VR: |
| 843 | return bluetoothHeadset.stopVoiceRecognition(device); |
| 844 | default: |
| 845 | return false; |
| 846 | } |
| 847 | } |
| 848 | |
| 849 | private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, |
| 850 | BluetoothDevice device, int scoAudioMode) { |
| 851 | switch (scoAudioMode) { |
| 852 | case SCO_MODE_RAW: |
| 853 | return bluetoothHeadset.connectAudio(); |
| 854 | case SCO_MODE_VIRTUAL_CALL: |
| 855 | return bluetoothHeadset.startScoUsingVirtualVoiceCall(); |
| 856 | case SCO_MODE_VR: |
| 857 | return bluetoothHeadset.startVoiceRecognition(device); |
| 858 | default: |
| 859 | return false; |
| 860 | } |
| 861 | } |
| 862 | |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 863 | private void checkScoAudioState() { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 864 | if (mBluetoothHeadset != null |
| 865 | && mBluetoothHeadsetDevice != null |
| 866 | && mScoAudioState == SCO_STATE_INACTIVE |
| 867 | && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) |
| 868 | != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { |
| 869 | mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 870 | } |
| 871 | } |
| 872 | |
| 873 | |
| 874 | private ScoClient getScoClient(IBinder cb, boolean create) { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 875 | for (ScoClient existingClient : mScoClients) { |
| 876 | if (existingClient.getBinder() == cb) { |
| 877 | return existingClient; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 878 | } |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 879 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 880 | if (create) { |
| 881 | ScoClient newClient = new ScoClient(cb); |
| 882 | mScoClients.add(newClient); |
| 883 | return newClient; |
| 884 | } |
| 885 | return null; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 886 | } |
| 887 | |
| 888 | private void clearAllScoClients(int exceptPid, boolean stopSco) { |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 889 | ScoClient savedClient = null; |
| 890 | for (ScoClient cl : mScoClients) { |
| 891 | if (cl.getPid() != exceptPid) { |
| 892 | cl.clearCount(stopSco); |
| 893 | } else { |
| 894 | savedClient = cl; |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 895 | } |
Jean-Michel Trivi | c4b9c96 | 2019-02-01 09:58:54 -0800 | [diff] [blame] | 896 | } |
| 897 | mScoClients.clear(); |
| 898 | if (savedClient != null) { |
| 899 | mScoClients.add(savedClient); |
Jean-Michel Trivi | 5885037 | 2018-09-14 16:01:28 -0700 | [diff] [blame] | 900 | } |
| 901 | } |
| 902 | |
| 903 | private boolean getBluetoothHeadset() { |
| 904 | boolean result = false; |
| 905 | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| 906 | if (adapter != null) { |
| 907 | result = adapter.getProfileProxy(mDeviceBroker.getContext(), |
| 908 | mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); |
| 909 | } |
| 910 | // If we could not get a bluetooth headset proxy, send a failure message |
| 911 | // without delay to reset the SCO audio state and clear SCO clients. |
| 912 | // If we could get a proxy, send a delayed failure message that will reset our state |
| 913 | // in case we don't receive onServiceConnected(). |
| 914 | mDeviceBroker.handleFailureToConnectToBtHeadsetService( |
| 915 | result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0); |
| 916 | return result; |
| 917 | } |
| 918 | |
| 919 | private int mapBluetoothCodecToAudioFormat(int btCodecType) { |
| 920 | switch (btCodecType) { |
| 921 | case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: |
| 922 | return AudioSystem.AUDIO_FORMAT_SBC; |
| 923 | case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: |
| 924 | return AudioSystem.AUDIO_FORMAT_AAC; |
| 925 | case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: |
| 926 | return AudioSystem.AUDIO_FORMAT_APTX; |
| 927 | case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: |
| 928 | return AudioSystem.AUDIO_FORMAT_APTX_HD; |
| 929 | case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: |
| 930 | return AudioSystem.AUDIO_FORMAT_LDAC; |
| 931 | default: |
| 932 | return AudioSystem.AUDIO_FORMAT_DEFAULT; |
| 933 | } |
| 934 | } |
| 935 | } |