blob: 350df11621e64c06741af69226086dfa4c6be547 [file] [log] [blame]
/*
* Copyright 2019 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.audio;
import static com.android.server.audio.AudioService.CONNECTION_STATE_CONNECTED;
import static com.android.server.audio.AudioService.CONNECTION_STATE_DISCONNECTED;
import android.annotation.NonNull;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
/** @hide */
/*package*/ final class AudioDeviceBroker {
private static final String TAG = "AS.AudioDeviceBroker";
private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s
/*package*/ static final int BTA2DP_DOCK_TIMEOUT_MS = 8000;
// Timeout for connection to bluetooth headset service
/*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
private final @NonNull AudioService mAudioService;
private final @NonNull Context mContext;
/** Forced device usage for communications sent to AudioSystem */
private int mForcedUseForComm;
/**
* Externally reported force device usage state returned by getters: always consistent
* with requests by setters */
private int mForcedUseForCommExt;
// Manages all connected devices, only ever accessed on the message loop
private final AudioDeviceInventory mDeviceInventory;
// Manages notifications to BT service
private final BtHelper mBtHelper;
//-------------------------------------------------------------------
// we use a different lock than mDeviceStateLock so as not to create
// lock contention between enqueueing a message and handling them
private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
@GuardedBy("sLastDeviceConnectionMsgTimeLock")
private static long sLastDeviceConnectMsgTime = 0;
// General lock to be taken whenever the state of the audio devices is to be checked or changed
private final Object mDeviceStateLock = new Object();
// Request to override default use of A2DP for media.
@GuardedBy("mDeviceStateLock")
private boolean mBluetoothA2dpEnabled;
// lock always taken when accessing AudioService.mSetModeDeathHandlers
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
/*package*/ final Object mSetModeLock = new Object();
//-------------------------------------------------------------------
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
mContext = context;
mAudioService = service;
setupMessaging(context);
mBtHelper = new BtHelper(this);
mDeviceInventory = new AudioDeviceInventory(this);
mForcedUseForComm = AudioSystem.FORCE_NONE;
mForcedUseForCommExt = mForcedUseForComm;
}
/*package*/ Context getContext() {
return mContext;
}
//---------------------------------------------------------------------
// Communication from AudioService
// All methods are asynchronous and never block
// All permission checks are done in AudioService, all incoming calls are considered "safe"
// All post* methods are asynchronous
/*package*/ void onSystemReady() {
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.onSystemReady();
}
}
}
/*package*/ void onAudioServerDied() {
// Restore forced usage for communications and record
synchronized (mDeviceStateLock) {
AudioSystem.setParameters(
"BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
}
// restore devices
sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
}
/*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
useCase, config, eventSource);
}
/*package*/ void toggleHdmiIfConnected_Async() {
sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
}
/*package*/ void disconnectAllBluetoothProfiles() {
synchronized (mDeviceStateLock) {
mBtHelper.disconnectAllBluetoothProfiles();
}
}
/**
* Handle BluetoothHeadset intents where the action is one of
* {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
* {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
* @param intent
*/
/*package*/ void receiveBtEvent(@NonNull Intent intent) {
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.receiveBtEvent(intent);
}
}
}
/*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
synchronized (mDeviceStateLock) {
if (mBluetoothA2dpEnabled == on) {
return;
}
mBluetoothA2dpEnabled = on;
mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
AudioSystem.FOR_MEDIA,
mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
source);
}
}
/**
* Turns speakerphone on/off
* @param on
* @param eventSource for logging purposes
* @return true if speakerphone state changed
*/
/*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
synchronized (mDeviceStateLock) {
final boolean wasOn = isSpeakerphoneOn();
if (on) {
if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
}
mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
} else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
mForcedUseForComm = AudioSystem.FORCE_NONE;
}
mForcedUseForCommExt = mForcedUseForComm;
setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
return (wasOn != isSpeakerphoneOn());
}
}
/*package*/ boolean isSpeakerphoneOn() {
synchronized (mDeviceStateLock) {
return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
}
}
/*package*/ void setWiredDeviceConnectionState(int type,
@AudioService.ConnectionState int state, String address, String name,
String caller) {
//TODO move logging here just like in setBluetooth* methods
synchronized (mDeviceStateLock) {
mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
}
}
private static final class BtDeviceConnectionInfo {
final @NonNull BluetoothDevice mDevice;
final @AudioService.BtProfileConnectionState int mState;
final int mProfile;
final boolean mSupprNoisy;
final int mVolume;
BtDeviceConnectionInfo(@NonNull BluetoothDevice device,
@AudioService.BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int vol) {
mDevice = device;
mState = state;
mProfile = profile;
mSupprNoisy = suppressNoisyIntent;
mVolume = vol;
}
}
/*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int a2dpVolume) {
final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
suppressNoisyIntent, a2dpVolume);
// TODO add a check to try to remove unprocessed messages for the same device (the old
// check didn't work), and make sure it doesn't conflict with config change message
sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
}
private static final class HearingAidDeviceConnectionInfo {
final @NonNull BluetoothDevice mDevice;
final @AudioService.BtProfileConnectionState int mState;
final boolean mSupprNoisy;
final int mMusicDevice;
final @NonNull String mEventSource;
HearingAidDeviceConnectionInfo(@NonNull BluetoothDevice device,
@AudioService.BtProfileConnectionState int state,
boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
mDevice = device;
mState = state;
mSupprNoisy = suppressNoisyIntent;
mMusicDevice = musicDevice;
mEventSource = eventSource;
}
}
/*package*/ void postBluetoothHearingAidDeviceConnectionState(
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
final HearingAidDeviceConnectionInfo info = new HearingAidDeviceConnectionInfo(
device, state, suppressNoisyIntent, musicDevice, eventSource);
sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
}
// never called by system components
/*package*/ void setBluetoothScoOnByApp(boolean on) {
synchronized (mDeviceStateLock) {
mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
}
}
/*package*/ boolean isBluetoothScoOnForApp() {
synchronized (mDeviceStateLock) {
return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
}
}
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
//Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
synchronized (mDeviceStateLock) {
if (on) {
// do not accept SCO ON if SCO audio is not connected
if (!mBtHelper.isBluetoothScoOn()) {
mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
return;
}
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
} else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
mForcedUseForComm = AudioSystem.FORCE_NONE;
}
mForcedUseForCommExt = mForcedUseForComm;
AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
}
// Un-mute ringtone stream volume
mAudioService.postUpdateRingerModeServiceInt();
}
/*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.startWatchingRoutes(observer);
}
}
/*package*/ AudioRoutesInfo getCurAudioRoutes() {
synchronized (mDeviceStateLock) {
return mDeviceInventory.getCurAudioRoutes();
}
}
/*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
synchronized (mDeviceStateLock) {
return mBtHelper.isAvrcpAbsoluteVolumeSupported();
}
}
/*package*/ boolean isBluetoothA2dpOn() {
synchronized (mDeviceStateLock) {
return mBluetoothA2dpEnabled;
}
}
/*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
}
/*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
}
/*package*/ void postDisconnectBluetoothSco(int exceptPid) {
sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
}
/*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
}
@GuardedBy("mSetModeLock")
/*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
@NonNull String eventSource) {
synchronized (mDeviceStateLock) {
mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
}
}
@GuardedBy("mSetModeLock")
/*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
synchronized (mDeviceStateLock) {
mBtHelper.stopBluetoothScoForClient(cb, eventSource);
}
}
//---------------------------------------------------------------------
// Communication with (to) AudioService
//TODO check whether the AudioService methods are candidates to move here
/*package*/ void postAccessoryPlugMediaUnmute(int device) {
mAudioService.postAccessoryPlugMediaUnmute(device);
}
/*package*/ int getVssVolumeForDevice(int streamType, int device) {
return mAudioService.getVssVolumeForDevice(streamType, device);
}
/*package*/ int getModeOwnerPid() {
return mAudioService.getModeOwnerPid();
}
/*package*/ int getDeviceForStream(int streamType) {
return mAudioService.getDeviceForStream(streamType);
}
/*package*/ void postApplyVolumeOnDevice(int streamType, int device, String caller) {
mAudioService.postApplyVolumeOnDevice(streamType, device, caller);
}
/*package*/ void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device,
String caller) {
mAudioService.postSetVolumeIndexOnDevice(streamType, vssVolIndex, device, caller);
}
/*packages*/ void postObserveDevicesForAllStreams() {
mAudioService.postObserveDevicesForAllStreams();
}
/*package*/ boolean isInCommunication() {
return mAudioService.isInCommunication();
}
/*package*/ boolean hasMediaDynamicPolicy() {
return mAudioService.hasMediaDynamicPolicy();
}
/*package*/ ContentResolver getContentResolver() {
return mAudioService.getContentResolver();
}
/*package*/ void checkMusicActive(int deviceType, String caller) {
mAudioService.checkMusicActive(deviceType, caller);
}
/*package*/ void checkVolumeCecOnHdmiConnection(
@AudioService.ConnectionState int state, String caller) {
mAudioService.postCheckVolumeCecOnHdmiConnection(state, caller);
}
/*package*/ boolean hasAudioFocusUsers() {
return mAudioService.hasAudioFocusUsers();
}
//---------------------------------------------------------------------
// Message handling on behalf of helper classes
/*package*/ void postBroadcastScoConnectionState(int state) {
sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
}
/*package*/ void postBroadcastBecomingNoisy() {
sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
}
/*package*/ void postA2dpSinkConnection(int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
state, btDeviceInfo, delay);
}
/*package*/ void postA2dpSourceConnection(int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
state, btDeviceInfo, delay);
}
/*package*/ void postSetWiredDeviceConnectionState(
AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
}
/*package*/ void postSetHearingAidConnectionState(
@AudioService.BtProfileConnectionState int state,
@NonNull BluetoothDevice device, int delay) {
sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
state,
device,
delay);
}
/*package*/ void postDisconnectA2dp() {
sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
}
/*package*/ void postDisconnectA2dpSink() {
sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
}
/*package*/ void postDisconnectHearingAid() {
sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
}
/*package*/ void postDisconnectHeadset() {
sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
}
/*package*/ void postBtA2dpProfileConnected(BluetoothA2dp a2dpProfile) {
sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
}
/*package*/ void postBtA2dpSinkProfileConnected(BluetoothProfile profile) {
sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
}
/*package*/ void postBtHeasetProfileConnected(BluetoothHeadset headsetProfile) {
sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE, headsetProfile);
}
/*package*/ void postBtHearingAidProfileConnected(BluetoothHearingAid hearingAidProfile) {
sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
hearingAidProfile);
}
/*package*/ void postScoClientDied(Object obj) {
sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
}
//---------------------------------------------------------------------
// Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
// only call from a "handle"* method or "on"* method
// Handles request to override default use of A2DP for media.
//@GuardedBy("mConnectedDevices")
/*package*/ void setBluetoothA2dpOnInt(boolean on, String source) {
// for logging only
final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).append(" src:").append(source).toString();
synchronized (mDeviceStateLock) {
mBluetoothA2dpEnabled = on;
mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
onSetForceUse(
AudioSystem.FOR_MEDIA,
mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
eventSource);
}
}
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
String deviceName) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
}
}
@GuardedBy("mDeviceStateLock")
/*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
final int intState = (state == BluetoothA2dp.STATE_CONNECTED)
? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;
final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
AudioSystem.DEVICE_NONE);
final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
+ " state= " + state
+ " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
}
sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
state, btDeviceInfo, delay);
}
/*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
btDeviceInfo);
}
/*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
}
/*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
}
/*package*/ void postReportNewRoutes() {
sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
}
/*package*/ void cancelA2dpDockTimeout() {
mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
}
/*package*/ void postA2dpActiveDeviceChange(
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
}
/*package*/ boolean hasScheduledA2dpDockTimeout() {
return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
}
// must be called synchronized on mConnectedDevices
/*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
}
/*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
}
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
synchronized (mDeviceStateLock) {
mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
}
}
/*package*/ boolean getBluetoothA2dpEnabled() {
synchronized (mDeviceStateLock) {
return mBluetoothA2dpEnabled;
}
}
/*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
synchronized (mDeviceStateLock) {
return mBtHelper.getA2dpCodec(device);
}
}
//---------------------------------------------------------------------
// Internal handling of messages
// These methods are ALL synchronous, in response to message handling in BrokerHandler
// Blocking in any of those will block the message queue
private void onSetForceUse(int useCase, int config, String eventSource) {
if (useCase == AudioSystem.FOR_MEDIA) {
postReportNewRoutes();
}
AudioService.sForceUseLogger.log(
new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
AudioSystem.setForceUse(useCase, config);
}
private void onSendBecomingNoisyIntent() {
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
}
//---------------------------------------------------------------------
// Message handling
private BrokerHandler mBrokerHandler;
private BrokerThread mBrokerThread;
private PowerManager.WakeLock mBrokerEventWakeLock;
private void setupMessaging(Context ctxt) {
final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE);
mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"handleAudioDeviceEvent");
mBrokerThread = new BrokerThread();
mBrokerThread.start();
waitForBrokerHandlerCreation();
}
private void waitForBrokerHandlerCreation() {
synchronized (this) {
while (mBrokerHandler == null) {
try {
wait();
} catch (InterruptedException e) {
Log.e(TAG, "Interruption while waiting on BrokerHandler");
}
}
}
}
/** Class that handles the device broker's message queue */
private class BrokerThread extends Thread {
BrokerThread() {
super("AudioDeviceBroker");
}
@Override
public void run() {
// Set this thread up so the handler will work on it
Looper.prepare();
synchronized (AudioDeviceBroker.this) {
mBrokerHandler = new BrokerHandler();
// Notify that the handler has been created
AudioDeviceBroker.this.notify();
}
Looper.loop();
}
}
/** Class that handles the message queue */
private class BrokerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESTORE_DEVICES:
synchronized (mDeviceStateLock) {
mDeviceInventory.onRestoreDevices();
mBtHelper.onAudioServerDiedRestoreA2dp();
}
break;
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
synchronized (mDeviceStateLock) {
mDeviceInventory.onSetWiredDeviceConnectionState(
(AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
}
break;
case MSG_I_BROADCAST_BT_CONNECTION_STATE:
synchronized (mDeviceStateLock) {
mBtHelper.onBroadcastScoConnectionState(msg.arg1);
}
break;
case MSG_IIL_SET_FORCE_USE: // intended fall-through
case MSG_IIL_SET_FORCE_BT_A2DP_USE:
onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
break;
case MSG_REPORT_NEW_ROUTES:
synchronized (mDeviceStateLock) {
mDeviceInventory.onReportNewRoutes();
}
break;
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
synchronized (mDeviceStateLock) {
mDeviceInventory.onSetA2dpSinkConnectionState(
(BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
}
break;
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
synchronized (mDeviceStateLock) {
mDeviceInventory.onSetA2dpSourceConnectionState(
(BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
}
break;
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
synchronized (mDeviceStateLock) {
mDeviceInventory.onSetHearingAidConnectionState(
(BluetoothDevice) msg.obj, msg.arg1,
mAudioService.getHearingAidStreamType());
}
break;
case MSG_BT_HEADSET_CNCT_FAILED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.resetBluetoothSco();
}
}
break;
case MSG_IL_BTA2DP_DOCK_TIMEOUT:
// msg.obj == address of BTA2DP device
synchronized (mDeviceStateLock) {
mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
}
break;
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
final int a2dpCodec;
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
synchronized (mDeviceStateLock) {
a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
onSendBecomingNoisyIntent();
break;
case MSG_II_SET_HEARING_AID_VOLUME:
synchronized (mDeviceStateLock) {
mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
}
break;
case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
synchronized (mDeviceStateLock) {
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
}
break;
case MSG_I_DISCONNECT_BT_SCO:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.disconnectBluetoothSco(msg.arg1);
}
}
break;
case MSG_L_SCOCLIENT_DIED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.scoClientDied(msg.obj);
}
}
break;
case MSG_TOGGLE_HDMI:
synchronized (mDeviceStateLock) {
mDeviceInventory.onToggleHdmi();
}
break;
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
synchronized (mDeviceStateLock) {
mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
(BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
}
break;
case MSG_DISCONNECT_A2DP:
synchronized (mDeviceStateLock) {
mDeviceInventory.disconnectA2dp();
}
break;
case MSG_DISCONNECT_A2DP_SINK:
synchronized (mDeviceStateLock) {
mDeviceInventory.disconnectA2dpSink();
}
break;
case MSG_DISCONNECT_BT_HEARING_AID:
synchronized (mDeviceStateLock) {
mDeviceInventory.disconnectHearingAid();
}
break;
case MSG_DISCONNECT_BT_HEADSET:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.disconnectHeadset();
}
}
break;
case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
synchronized (mDeviceStateLock) {
mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
}
break;
case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK:
synchronized (mDeviceStateLock) {
mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
}
break;
case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID:
synchronized (mDeviceStateLock) {
mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
}
break;
case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
}
}
break;
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: {
final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj;
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent "
+ " state=" + info.mState
// only querying address as this is the only readily available
// field on the device
+ " addr=" + info.mDevice.getAddress()
+ " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy
+ " vol=" + info.mVolume)).printLog(TAG));
synchronized (mDeviceStateLock) {
mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
AudioSystem.DEVICE_NONE, info.mVolume);
}
} break;
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: {
final HearingAidDeviceConnectionInfo info =
(HearingAidDeviceConnectionInfo) msg.obj;
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"setHearingAidDeviceConnectionState state=" + info.mState
+ " addr=" + info.mDevice.getAddress()
+ " supprNoisy=" + info.mSupprNoisy
+ " src=" + info.mEventSource)).printLog(TAG));
synchronized (mDeviceStateLock) {
mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
}
} break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
if (isMessageHandledUnderWakelock(msg.what)) {
try {
mBrokerEventWakeLock.release();
} catch (Exception e) {
Log.e(TAG, "Exception releasing wakelock", e);
}
}
}
}
// List of all messages. If a message has be handled under wakelock, add it to
// the isMessageHandledUnderWakelock(int) method
// Naming of msg indicates arguments, using JNI argument grammar
// (e.g. II indicates two int args, IL indicates int and Obj arg)
private static final int MSG_RESTORE_DEVICES = 1;
private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
private static final int MSG_IIL_SET_FORCE_USE = 4;
private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
private static final int MSG_REPORT_NEW_ROUTES = 13;
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
private static final int MSG_I_DISCONNECT_BT_SCO = 16;
private static final int MSG_TOGGLE_HDMI = 17;
private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
private static final int MSG_DISCONNECT_A2DP = 19;
private static final int MSG_DISCONNECT_A2DP_SINK = 20;
private static final int MSG_DISCONNECT_BT_HEARING_AID = 21;
private static final int MSG_DISCONNECT_BT_HEADSET = 22;
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP = 23;
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK = 24;
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID = 25;
private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET = 26;
// process external command to (dis)connect an A2DP device
private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27;
// process external command to (dis)connect a hearing aid device
private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28;
// a ScoClient died in BtHelper
private static final int MSG_L_SCOCLIENT_DIED = 29;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
case MSG_IL_BTA2DP_DOCK_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
return true;
default:
return false;
}
}
// Message helper methods
// sendMsg() flags
/** If the msg is already queued, replace it with this one. */
private static final int SENDMSG_REPLACE = 0;
/** If the msg is already queued, ignore this one and leave the old. */
private static final int SENDMSG_NOOP = 1;
/** If the msg is already queued, queue this one and leave the old. */
private static final int SENDMSG_QUEUE = 2;
private void sendMsg(int msg, int existingMsgPolicy, int delay) {
sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
}
private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
}
private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
}
private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
}
private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
}
private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
}
private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
}
private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
}
private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
}
private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
}
private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
int delay) {
if (existingMsgPolicy == SENDMSG_REPLACE) {
mBrokerHandler.removeMessages(msg);
} else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) {
return;
}
if (isMessageHandledUnderWakelock(msg)) {
final long identity = Binder.clearCallingIdentity();
try {
mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS);
} catch (Exception e) {
Log.e(TAG, "Exception acquiring wakelock", e);
}
Binder.restoreCallingIdentity(identity);
}
synchronized (sLastDeviceConnectionMsgTimeLock) {
long time = SystemClock.uptimeMillis() + delay;
switch (msg) {
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_IL_BTA2DP_DOCK_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
if (sLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected
time = sLastDeviceConnectMsgTime + 30;
}
sLastDeviceConnectMsgTime = time;
break;
default:
break;
}
mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
time);
}
}
//-------------------------------------------------------------
// internal utilities
private void sendBroadcastToAll(Intent intent) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}