| /* |
| * 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 android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.compat.CompatChanges; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHeadset; |
| import android.bluetooth.BluetoothProfile; |
| import android.compat.annotation.ChangeId; |
| import android.compat.annotation.EnabledSince; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.media.AudioAttributes; |
| import android.media.AudioDeviceAttributes; |
| import android.media.AudioDeviceInfo; |
| import android.media.AudioManager; |
| import android.media.AudioRoutesInfo; |
| import android.media.AudioSystem; |
| import android.media.BluetoothProfileConnectionInfo; |
| import android.media.IAudioRoutesObserver; |
| import android.media.ICapturePresetDevicesRoleDispatcher; |
| import android.media.ICommunicationDeviceDispatcher; |
| import android.media.IStrategyNonDefaultDevicesDispatcher; |
| import android.media.IStrategyPreferredDevicesDispatcher; |
| import android.media.MediaMetrics; |
| import android.media.audiopolicy.AudioProductStrategy; |
| 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.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.PrintWriterPrinter; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.server.utils.EventLogger; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| |
| /** @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; |
| |
| // Delay before checking it music should be unmuted after processing an A2DP message |
| private static final int BTA2DP_MUTE_CHECK_DELAY_MS = 100; |
| |
| private final @NonNull AudioService mAudioService; |
| private final @NonNull Context mContext; |
| private final @NonNull AudioSystemAdapter mAudioSystem; |
| |
| /** ID for Communication strategy retrieved form audio policy manager */ |
| /*package*/ int mCommunicationStrategyId = -1; |
| |
| /** ID for Accessibility strategy retrieved form audio policy manager */ |
| private int mAccessibilityStrategyId = -1; |
| |
| |
| /** Active communication device reported by audio policy manager */ |
| /*package*/ AudioDeviceInfo mActiveCommunicationDevice; |
| /** Last preferred device set for communication strategy */ |
| private AudioDeviceAttributes mPreferredCommunicationDevice; |
| |
| // Manages all connected devices, only ever accessed on the message loop |
| private final AudioDeviceInventory mDeviceInventory; |
| // Manages notifications to BT service |
| private final BtHelper mBtHelper; |
| // Adapter for system_server-reserved operations |
| private final SystemServerAdapter mSystemServer; |
| |
| |
| //------------------------------------------------------------------- |
| // 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(); |
| |
| /** AudioModeInfo contains information on current audio mode owner |
| * communicated by AudioService */ |
| /* package */ static final class AudioModeInfo { |
| /** Current audio mode */ |
| final int mMode; |
| /** PID of current audio mode owner */ |
| final int mPid; |
| /** UID of current audio mode owner */ |
| final int mUid; |
| |
| AudioModeInfo(int mode, int pid, int uid) { |
| mMode = mode; |
| mPid = pid; |
| mUid = uid; |
| } |
| |
| @Override |
| public String toString() { |
| return "AudioModeInfo: mMode=" + AudioSystem.modeToString(mMode) |
| + ", mPid=" + mPid |
| + ", mUid=" + mUid; |
| } |
| }; |
| |
| private AudioModeInfo mAudioModeOwner = new AudioModeInfo(AudioSystem.MODE_NORMAL, 0, 0); |
| |
| /** |
| * Indicates that default communication device is chosen by routing rules in audio policy |
| * manager and not forced by AudioDeviceBroker. |
| */ |
| @ChangeId |
| @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2) |
| public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L; |
| |
| //------------------------------------------------------------------- |
| /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, |
| @NonNull AudioSystemAdapter audioSystem) { |
| mContext = context; |
| mAudioService = service; |
| mBtHelper = new BtHelper(this); |
| mDeviceInventory = new AudioDeviceInventory(this); |
| mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext); |
| mAudioSystem = audioSystem; |
| |
| init(); |
| } |
| |
| /** for test purposes only, inject AudioDeviceInventory and adapter for operations running |
| * in system_server */ |
| AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, |
| @NonNull AudioDeviceInventory mockDeviceInventory, |
| @NonNull SystemServerAdapter mockSystemServer, |
| @NonNull AudioSystemAdapter audioSystem) { |
| mContext = context; |
| mAudioService = service; |
| mBtHelper = new BtHelper(this); |
| mDeviceInventory = mockDeviceInventory; |
| mSystemServer = mockSystemServer; |
| mAudioSystem = audioSystem; |
| |
| init(); |
| } |
| |
| private void initRoutingStrategyIds() { |
| List<AudioProductStrategy> strategies = AudioProductStrategy.getAudioProductStrategies(); |
| mCommunicationStrategyId = -1; |
| mAccessibilityStrategyId = -1; |
| for (AudioProductStrategy strategy : strategies) { |
| if (mCommunicationStrategyId == -1 |
| && strategy.getAudioAttributesForLegacyStreamType( |
| AudioSystem.STREAM_VOICE_CALL) != null) { |
| mCommunicationStrategyId = strategy.getId(); |
| } |
| if (mAccessibilityStrategyId == -1 |
| && strategy.getAudioAttributesForLegacyStreamType( |
| AudioSystem.STREAM_ACCESSIBILITY) != null) { |
| mAccessibilityStrategyId = strategy.getId(); |
| } |
| } |
| } |
| |
| private void init() { |
| setupMessaging(mContext); |
| |
| initAudioHalBluetoothState(); |
| initRoutingStrategyIds(); |
| mPreferredCommunicationDevice = null; |
| updateActiveCommunicationDevice(); |
| |
| mSystemServer.registerUserStartedReceiver(mContext); |
| } |
| |
| /*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) { |
| mAudioModeOwner = mAudioService.getAudioModeOwner(); |
| mBtHelper.onSystemReady(); |
| } |
| } |
| } |
| |
| /*package*/ void 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 |
| */ |
| /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { |
| |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid); |
| } |
| postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( |
| cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), |
| on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); |
| } |
| |
| /** |
| * Select device for use for communication use cases. |
| * @param cb Client binder for death detection |
| * @param pid Client pid |
| * @param device Device selected or null to unselect. |
| * @param eventSource for logging purposes |
| */ |
| |
| private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000; |
| |
| /*package*/ boolean setCommunicationDevice( |
| IBinder cb, int pid, AudioDeviceInfo device, String eventSource) { |
| |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid); |
| } |
| |
| AudioDeviceAttributes deviceAttr = |
| (device != null) ? new AudioDeviceAttributes(device) : null; |
| CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr, |
| device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true); |
| postSetCommunicationDeviceForClient(deviceInfo); |
| boolean status; |
| synchronized (deviceInfo) { |
| final long start = System.currentTimeMillis(); |
| long elapsed = 0; |
| while (deviceInfo.mWaitForStatus) { |
| try { |
| deviceInfo.wait(SET_COMMUNICATION_DEVICE_TIMEOUT_MS - elapsed); |
| } catch (InterruptedException e) { |
| elapsed = System.currentTimeMillis() - start; |
| if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) { |
| deviceInfo.mStatus = false; |
| deviceInfo.mWaitForStatus = false; |
| } |
| } |
| } |
| status = deviceInfo.mStatus; |
| } |
| return status; |
| } |
| |
| /** |
| * Sets or resets the communication device for matching client. If no client matches and the |
| * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop. |
| * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo} |
| * @return true if the communication device is set or reset |
| */ |
| @GuardedBy("mDeviceStateLock") |
| /*package*/ boolean onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo); |
| } |
| if (!deviceInfo.mOn) { |
| CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid); |
| if (client == null || (deviceInfo.mDevice != null |
| && !deviceInfo.mDevice.equals(client.getDevice()))) { |
| return false; |
| } |
| } |
| |
| AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null; |
| setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device, |
| deviceInfo.mScoAudioMode, deviceInfo.mEventSource); |
| return true; |
| } |
| |
| @GuardedBy("mDeviceStateLock") |
| /*package*/ void setCommunicationRouteForClient( |
| IBinder cb, int pid, AudioDeviceAttributes device, |
| int scoAudioMode, String eventSource) { |
| |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "setCommunicationRouteForClient: device: " + device); |
| } |
| AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( |
| "setCommunicationRouteForClient for pid: " + pid |
| + " device: " + device |
| + " from API: " + eventSource)).printLog(TAG)); |
| |
| final boolean wasBtScoRequested = isBluetoothScoRequested(); |
| CommunicationRouteClient client; |
| |
| |
| // Save previous client route in case of failure to start BT SCO audio |
| AudioDeviceAttributes prevClientDevice = null; |
| client = getCommunicationRouteClientForPid(pid); |
| if (client != null) { |
| prevClientDevice = client.getDevice(); |
| } |
| |
| if (device != null) { |
| client = addCommunicationRouteClient(cb, pid, device); |
| if (client == null) { |
| Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: " |
| + pid + " and device: " + device); |
| } |
| } else { |
| client = removeCommunicationRouteClient(cb, true); |
| } |
| if (client == null) { |
| return; |
| } |
| |
| boolean isBtScoRequested = isBluetoothScoRequested(); |
| if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { |
| if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { |
| Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: " |
| + pid); |
| // clean up or restore previous client selection |
| if (prevClientDevice != null) { |
| addCommunicationRouteClient(cb, pid, prevClientDevice); |
| } else { |
| removeCommunicationRouteClient(cb, true); |
| } |
| postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); |
| } |
| } else if (!isBtScoRequested && wasBtScoRequested) { |
| mBtHelper.stopBluetoothSco(eventSource); |
| } |
| |
| updateCommunicationRoute(eventSource); |
| } |
| |
| /** |
| * Returns the communication client with the highest priority: |
| * - 1) the client which is currently also controlling the audio mode |
| * - 2) the first client in the stack if there is no audio mode owner |
| * - 3) no client otherwise |
| * @return CommunicationRouteClient the client driving the communication use case routing. |
| */ |
| @GuardedBy("mDeviceStateLock") |
| private CommunicationRouteClient topCommunicationRouteClient() { |
| for (CommunicationRouteClient crc : mCommunicationRouteClients) { |
| if (crc.getPid() == mAudioModeOwner.mPid) { |
| return crc; |
| } |
| } |
| if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) { |
| return mCommunicationRouteClients.get(0); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the device currently requested for communication use case. |
| * Use the device requested by the communication route client selected by |
| * {@link #topCommunicationRouteClient()} if any or none otherwise. |
| * @return AudioDeviceAttributes the requested device for communication. |
| */ |
| @GuardedBy("mDeviceStateLock") |
| private AudioDeviceAttributes requestedCommunicationDevice() { |
| CommunicationRouteClient crc = topCommunicationRouteClient(); |
| AudioDeviceAttributes device = crc != null ? crc.getDevice() : null; |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "requestedCommunicationDevice: " |
| + device + " mAudioModeOwner: " + mAudioModeOwner.toString()); |
| } |
| return device; |
| } |
| |
| private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = { |
| AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, |
| AudioDeviceInfo.TYPE_BLUETOOTH_SCO, |
| AudioDeviceInfo.TYPE_WIRED_HEADSET, |
| AudioDeviceInfo.TYPE_USB_HEADSET, |
| AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, |
| AudioDeviceInfo.TYPE_WIRED_HEADPHONES, |
| AudioDeviceInfo.TYPE_HEARING_AID, |
| AudioDeviceInfo.TYPE_BLE_HEADSET, |
| AudioDeviceInfo.TYPE_USB_DEVICE, |
| AudioDeviceInfo.TYPE_BLE_SPEAKER, |
| AudioDeviceInfo.TYPE_LINE_ANALOG, |
| AudioDeviceInfo.TYPE_HDMI, |
| AudioDeviceInfo.TYPE_AUX_LINE |
| }; |
| |
| /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { |
| for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { |
| if (device.getType() == type) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() { |
| ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>(); |
| AudioDeviceInfo[] allDevices = |
| AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); |
| for (AudioDeviceInfo device : allDevices) { |
| if (isValidCommunicationDevice(device)) { |
| commDevices.add(device); |
| } |
| } |
| return commDevices; |
| } |
| |
| private @Nullable AudioDeviceInfo getCommunicationDeviceOfType(int type) { |
| return getAvailableCommunicationDevices().stream().filter(d -> d.getType() == type) |
| .findFirst().orElse(null); |
| } |
| |
| /** |
| * Returns the device currently requested for communication use case. |
| * @return AudioDeviceInfo the requested device for communication. |
| */ |
| /* package */ AudioDeviceInfo getCommunicationDevice() { |
| synchronized (mDeviceStateLock) { |
| updateActiveCommunicationDevice(); |
| AudioDeviceInfo device = mActiveCommunicationDevice; |
| // make sure we return a valid communication device (i.e. a device that is allowed by |
| // setCommunicationDevice()) for consistency. |
| if (device != null) { |
| // a digital dock is used instead of the speaker in speakerphone mode and should |
| // be reflected as such |
| if (device.getType() == AudioDeviceInfo.TYPE_DOCK) { |
| device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); |
| } |
| } |
| // Try to default to earpiece when current communication device is not valid. This can |
| // happen for instance if no call is active. If no earpiece device is available take the |
| // first valid communication device |
| if (device == null || !AudioDeviceBroker.isValidCommunicationDevice(device)) { |
| device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE); |
| if (device == null) { |
| List<AudioDeviceInfo> commDevices = getAvailableCommunicationDevices(); |
| if (!commDevices.isEmpty()) { |
| device = commDevices.get(0); |
| } |
| } |
| } |
| return device; |
| } |
| } |
| |
| /** |
| * Updates currently active communication device (mActiveCommunicationDevice). |
| */ |
| @GuardedBy("mDeviceStateLock") |
| void updateActiveCommunicationDevice() { |
| AudioDeviceAttributes device = preferredCommunicationDevice(); |
| if (device == null) { |
| AudioAttributes attr = |
| AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType( |
| AudioSystem.STREAM_VOICE_CALL); |
| List<AudioDeviceAttributes> devices = mAudioSystem.getDevicesForAttributes( |
| attr, false /* forVolume */); |
| if (devices.isEmpty()) { |
| if (mAudioService.isPlatformVoice()) { |
| Log.w(TAG, |
| "updateActiveCommunicationDevice(): no device for phone strategy"); |
| } |
| mActiveCommunicationDevice = null; |
| return; |
| } |
| device = devices.get(0); |
| } |
| mActiveCommunicationDevice = AudioManager.getDeviceInfoFromTypeAndAddress( |
| device.getType(), device.getAddress()); |
| } |
| |
| /** |
| * Indicates if the device which type is passed as argument is currently resquested to be used |
| * for communication. |
| * @param deviceType the device type the query applies to. |
| * @return true if this device type is requested for communication. |
| */ |
| private boolean isDeviceRequestedForCommunication(int deviceType) { |
| synchronized (mDeviceStateLock) { |
| AudioDeviceAttributes device = requestedCommunicationDevice(); |
| return device != null && device.getType() == deviceType; |
| } |
| } |
| |
| /** |
| * Indicates if the device which type is passed as argument is currently either resquested |
| * to be used for communication or selected for an other reason (e.g bluetooth SCO audio |
| * is active for SCO device). |
| * @param deviceType the device type the query applies to. |
| * @return true if this device type is requested for communication. |
| */ |
| private boolean isDeviceOnForCommunication(int deviceType) { |
| synchronized (mDeviceStateLock) { |
| AudioDeviceAttributes device = preferredCommunicationDevice(); |
| return device != null && device.getType() == deviceType; |
| } |
| } |
| |
| /** |
| * Indicates if the device which type is passed as argument is active for communication. |
| * Active means not only currently used by audio policy manager for communication strategy |
| * but also explicitly requested for use by communication strategy. |
| * @param deviceType the device type the query applies to. |
| * @return true if this device type is requested for communication. |
| */ |
| private boolean isDeviceActiveForCommunication(int deviceType) { |
| return mActiveCommunicationDevice != null |
| && mActiveCommunicationDevice.getType() == deviceType |
| && mPreferredCommunicationDevice != null |
| && mPreferredCommunicationDevice.getType() == deviceType; |
| } |
| |
| /** |
| * Helper method on top of isDeviceRequestedForCommunication() indicating if |
| * speakerphone ON is currently requested or not. |
| * @return true if speakerphone ON requested, false otherwise. |
| */ |
| private boolean isSpeakerphoneRequested() { |
| return isDeviceRequestedForCommunication(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); |
| } |
| |
| /** |
| * Indicates if preferred route selection for communication is speakerphone. |
| * @return true if speakerphone is active, false otherwise. |
| */ |
| /*package*/ boolean isSpeakerphoneOn() { |
| return isDeviceOnForCommunication(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); |
| } |
| |
| private boolean isSpeakerphoneActive() { |
| return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); |
| } |
| |
| /** |
| * Helper method on top of isDeviceRequestedForCommunication() indicating if |
| * Bluetooth SCO ON is currently requested or not. |
| * @return true if Bluetooth SCO ON is requested, false otherwise. |
| */ |
| /*package*/ boolean isBluetoothScoRequested() { |
| return isDeviceRequestedForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO); |
| } |
| |
| /** |
| * Indicates if preferred route selection for communication is Bluetooth SCO. |
| * @return true if Bluetooth SCO is preferred , false otherwise. |
| */ |
| /*package*/ boolean isBluetoothScoOn() { |
| return isDeviceOnForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO); |
| } |
| |
| /*package*/ boolean isBluetoothScoActive() { |
| return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO); |
| } |
| |
| /*package*/ boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) { |
| synchronized (mDeviceStateLock) { |
| return mDeviceInventory.isDeviceConnected(device); |
| } |
| } |
| |
| /*package*/ void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, |
| @AudioService.ConnectionState int state, String caller) { |
| //TODO move logging here just like in setBluetooth* methods |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.setWiredDeviceConnectionState(attributes, state, caller); |
| } |
| } |
| |
| /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, |
| @AudioService.ConnectionState int state) { |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.setTestDeviceConnectionState(device, state); |
| } |
| } |
| |
| /*package*/ static final class BleVolumeInfo { |
| final int mIndex; |
| final int mMaxIndex; |
| final int mStreamType; |
| |
| BleVolumeInfo(int index, int maxIndex, int streamType) { |
| mIndex = index; |
| mMaxIndex = maxIndex; |
| mStreamType = streamType; |
| } |
| }; |
| |
| /*package*/ static final class BtDeviceChangedData { |
| final @Nullable BluetoothDevice mNewDevice; |
| final @Nullable BluetoothDevice mPreviousDevice; |
| final @NonNull BluetoothProfileConnectionInfo mInfo; |
| final @NonNull String mEventSource; |
| |
| BtDeviceChangedData(@Nullable BluetoothDevice newDevice, |
| @Nullable BluetoothDevice previousDevice, |
| @NonNull BluetoothProfileConnectionInfo info, @NonNull String eventSource) { |
| mNewDevice = newDevice; |
| mPreviousDevice = previousDevice; |
| mInfo = info; |
| mEventSource = eventSource; |
| } |
| |
| @Override |
| public String toString() { |
| return "BtDeviceChangedData profile=" + BluetoothProfile.getProfileName( |
| mInfo.getProfile()) |
| + ", switch device: [" + mPreviousDevice + "] -> [" + mNewDevice + "]"; |
| } |
| } |
| |
| /*package*/ static final class BtDeviceInfo { |
| final @NonNull BluetoothDevice mDevice; |
| final @AudioService.BtProfileConnectionState int mState; |
| final @AudioService.BtProfile int mProfile; |
| final boolean mSupprNoisy; |
| final int mVolume; |
| final boolean mIsLeOutput; |
| final @NonNull String mEventSource; |
| final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec; |
| final int mAudioSystemDevice; |
| final int mMusicDevice; |
| |
| BtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device, int state, |
| int audioDevice, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) { |
| mDevice = device; |
| mState = state; |
| mProfile = d.mInfo.getProfile(); |
| mSupprNoisy = d.mInfo.isSuppressNoisyIntent(); |
| mVolume = d.mInfo.getVolume(); |
| mIsLeOutput = d.mInfo.isLeOutput(); |
| mEventSource = d.mEventSource; |
| mAudioSystemDevice = audioDevice; |
| mMusicDevice = AudioSystem.DEVICE_NONE; |
| mCodec = codec; |
| } |
| |
| // constructor used by AudioDeviceBroker to search similar message |
| BtDeviceInfo(@NonNull BluetoothDevice device, int profile) { |
| mDevice = device; |
| mProfile = profile; |
| mEventSource = ""; |
| mMusicDevice = AudioSystem.DEVICE_NONE; |
| mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; |
| mAudioSystemDevice = 0; |
| mState = 0; |
| mSupprNoisy = false; |
| mVolume = -1; |
| mIsLeOutput = false; |
| } |
| |
| // constructor used by AudioDeviceInventory when config change failed |
| BtDeviceInfo(@NonNull BluetoothDevice device, int profile, int state, int musicDevice, |
| int audioSystemDevice) { |
| mDevice = device; |
| mProfile = profile; |
| mEventSource = ""; |
| mMusicDevice = musicDevice; |
| mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; |
| mAudioSystemDevice = audioSystemDevice; |
| mState = state; |
| mSupprNoisy = false; |
| mVolume = -1; |
| mIsLeOutput = false; |
| } |
| |
| BtDeviceInfo(@NonNull BtDeviceInfo src, int state) { |
| mDevice = src.mDevice; |
| mState = state; |
| mProfile = src.mProfile; |
| mSupprNoisy = src.mSupprNoisy; |
| mVolume = src.mVolume; |
| mIsLeOutput = src.mIsLeOutput; |
| mEventSource = src.mEventSource; |
| mAudioSystemDevice = src.mAudioSystemDevice; |
| mMusicDevice = src.mMusicDevice; |
| mCodec = src.mCodec; |
| } |
| |
| // redefine equality op so we can match messages intended for this device |
| @Override |
| public boolean equals(Object o) { |
| if (o == null) { |
| return false; |
| } |
| if (this == o) { |
| return true; |
| } |
| if (o instanceof BtDeviceInfo) { |
| return mProfile == ((BtDeviceInfo) o).mProfile |
| && mDevice.equals(((BtDeviceInfo) o).mDevice); |
| } |
| return false; |
| } |
| } |
| |
| BtDeviceInfo createBtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device, |
| int state) { |
| int audioDevice; |
| int codec = AudioSystem.AUDIO_FORMAT_DEFAULT; |
| switch (d.mInfo.getProfile()) { |
| case BluetoothProfile.A2DP_SINK: |
| audioDevice = AudioSystem.DEVICE_IN_BLUETOOTH_A2DP; |
| break; |
| case BluetoothProfile.A2DP: |
| audioDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; |
| synchronized (mDeviceStateLock) { |
| codec = mBtHelper.getA2dpCodec(device); |
| } |
| break; |
| case BluetoothProfile.HEARING_AID: |
| audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID; |
| break; |
| case BluetoothProfile.LE_AUDIO: |
| if (d.mInfo.isLeOutput()) { |
| audioDevice = AudioSystem.DEVICE_OUT_BLE_HEADSET; |
| } else { |
| audioDevice = AudioSystem.DEVICE_IN_BLE_HEADSET; |
| } |
| break; |
| case BluetoothProfile.LE_AUDIO_BROADCAST: |
| audioDevice = AudioSystem.DEVICE_OUT_BLE_BROADCAST; |
| break; |
| default: throw new IllegalArgumentException("Invalid profile " + d.mInfo.getProfile()); |
| } |
| return new BtDeviceInfo(d, device, state, audioDevice, codec); |
| } |
| |
| private void btMediaMetricRecord(@NonNull BluetoothDevice device, String state, |
| @NonNull BtDeviceChangedData data) { |
| final String name = TextUtils.emptyIfNull(device.getName()); |
| new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR |
| + "queueOnBluetoothActiveDeviceChanged") |
| .set(MediaMetrics.Property.STATE, state) |
| .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile()) |
| .set(MediaMetrics.Property.NAME, name) |
| .record(); |
| } |
| |
| /** |
| * will block on mDeviceStateLock, which is held during an A2DP (dis) connection |
| * not just a simple message post |
| * @param info struct with the (dis)connection information |
| */ |
| /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) { |
| if (data.mPreviousDevice != null |
| && data.mPreviousDevice.equals(data.mNewDevice)) { |
| final String name = TextUtils.emptyIfNull(data.mNewDevice.getName()); |
| new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR |
| + "queueOnBluetoothActiveDeviceChanged_update") |
| .set(MediaMetrics.Property.NAME, name) |
| .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile()) |
| .record(); |
| synchronized (mDeviceStateLock) { |
| postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice, |
| BluetoothProfile.STATE_CONNECTED)); |
| } |
| } else { |
| synchronized (mDeviceStateLock) { |
| if (data.mPreviousDevice != null) { |
| btMediaMetricRecord(data.mPreviousDevice, MediaMetrics.Value.DISCONNECTED, |
| data); |
| sendLMsgNoDelay(MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT, SENDMSG_QUEUE, |
| createBtDeviceInfo(data, data.mPreviousDevice, |
| BluetoothProfile.STATE_DISCONNECTED)); |
| } |
| if (data.mNewDevice != null) { |
| btMediaMetricRecord(data.mNewDevice, MediaMetrics.Value.CONNECTED, data); |
| sendLMsgNoDelay(MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT, SENDMSG_QUEUE, |
| createBtDeviceInfo(data, data.mNewDevice, |
| BluetoothProfile.STATE_CONNECTED)); |
| } |
| } |
| } |
| } |
| |
| // Lock protecting state variable related to Bluetooth audio state |
| private final Object mBluetoothAudioStateLock = new Object(); |
| |
| // Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn(). |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothScoOn; |
| // value of BT_SCO parameter currently applied to audio HAL. |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothScoOnApplied; |
| |
| // A2DP suspend state requested by AudioManager.setA2dpSuspended() API. |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothA2dpSuspendedExt; |
| // A2DP suspend state requested by AudioDeviceInventory. |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothA2dpSuspendedInt; |
| // value of BT_A2dpSuspendedSCO parameter currently applied to audio HAL. |
| |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothA2dpSuspendedApplied; |
| |
| // LE Audio suspend state requested by AudioManager.setLeAudioSuspended() API. |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothLeSuspendedExt; |
| // LE Audio suspend state requested by AudioDeviceInventory. |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothLeSuspendedInt; |
| // value of LeAudioSuspended parameter currently applied to audio HAL. |
| @GuardedBy("mBluetoothAudioStateLock") |
| private boolean mBluetoothLeSuspendedApplied; |
| |
| private void initAudioHalBluetoothState() { |
| synchronized (mBluetoothAudioStateLock) { |
| mBluetoothScoOnApplied = false; |
| AudioSystem.setParameters("BT_SCO=off"); |
| mBluetoothA2dpSuspendedApplied = false; |
| AudioSystem.setParameters("A2dpSuspended=false"); |
| mBluetoothLeSuspendedApplied = false; |
| AudioSystem.setParameters("LeAudioSuspended=false"); |
| } |
| } |
| |
| @GuardedBy("mBluetoothAudioStateLock") |
| private void updateAudioHalBluetoothState() { |
| if (mBluetoothScoOn != mBluetoothScoOnApplied) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothScoOn: " |
| + mBluetoothScoOn + ", mBluetoothScoOnApplied: " + mBluetoothScoOnApplied); |
| } |
| if (mBluetoothScoOn) { |
| if (!mBluetoothA2dpSuspendedApplied) { |
| AudioSystem.setParameters("A2dpSuspended=true"); |
| mBluetoothA2dpSuspendedApplied = true; |
| } |
| if (!mBluetoothLeSuspendedApplied) { |
| AudioSystem.setParameters("LeAudioSuspended=true"); |
| mBluetoothLeSuspendedApplied = true; |
| } |
| AudioSystem.setParameters("BT_SCO=on"); |
| } else { |
| AudioSystem.setParameters("BT_SCO=off"); |
| } |
| mBluetoothScoOnApplied = mBluetoothScoOn; |
| } |
| if (!mBluetoothScoOnApplied) { |
| if ((mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt) |
| != mBluetoothA2dpSuspendedApplied) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothA2dpSuspendedExt: " |
| + mBluetoothA2dpSuspendedExt |
| + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt |
| + ", mBluetoothA2dpSuspendedApplied: " |
| + mBluetoothA2dpSuspendedApplied); |
| } |
| mBluetoothA2dpSuspendedApplied = |
| mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt; |
| if (mBluetoothA2dpSuspendedApplied) { |
| AudioSystem.setParameters("A2dpSuspended=true"); |
| } else { |
| AudioSystem.setParameters("A2dpSuspended=false"); |
| } |
| } |
| if ((mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt) |
| != mBluetoothLeSuspendedApplied) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothLeSuspendedExt: " |
| + mBluetoothLeSuspendedExt |
| + ", mBluetoothLeSuspendedInt: " + mBluetoothLeSuspendedInt |
| + ", mBluetoothLeSuspendedApplied: " + mBluetoothLeSuspendedApplied); |
| } |
| mBluetoothLeSuspendedApplied = |
| mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt; |
| if (mBluetoothLeSuspendedApplied) { |
| AudioSystem.setParameters("LeAudioSuspended=true"); |
| } else { |
| AudioSystem.setParameters("LeAudioSuspended=false"); |
| } |
| } |
| } |
| } |
| |
| /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource); |
| } |
| synchronized (mBluetoothAudioStateLock) { |
| mBluetoothScoOn = on; |
| updateAudioHalBluetoothState(); |
| postUpdateCommunicationRouteClient(eventSource); |
| } |
| } |
| |
| /*package*/ void setA2dpSuspended(boolean enable, boolean internal, String eventSource) { |
| synchronized (mBluetoothAudioStateLock) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "setA2dpSuspended source: " + eventSource + ", enable: " |
| + enable + ", internal: " + internal |
| + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt |
| + ", mBluetoothA2dpSuspendedExt: " + mBluetoothA2dpSuspendedExt); |
| } |
| if (internal) { |
| mBluetoothA2dpSuspendedInt = enable; |
| } else { |
| mBluetoothA2dpSuspendedExt = enable; |
| } |
| updateAudioHalBluetoothState(); |
| } |
| } |
| |
| /*package*/ void clearA2dpSuspended() { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "clearA2dpSuspended"); |
| } |
| synchronized (mBluetoothAudioStateLock) { |
| mBluetoothA2dpSuspendedInt = false; |
| mBluetoothA2dpSuspendedExt = false; |
| updateAudioHalBluetoothState(); |
| } |
| } |
| |
| /*package*/ void setLeAudioSuspended(boolean enable, boolean internal, String eventSource) { |
| synchronized (mBluetoothAudioStateLock) { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "setLeAudioSuspended source: " + eventSource + ", enable: " |
| + enable + ", internal: " + internal |
| + ", mBluetoothLeSuspendedInt: " + mBluetoothA2dpSuspendedInt |
| + ", mBluetoothLeSuspendedExt: " + mBluetoothA2dpSuspendedExt); |
| } |
| if (internal) { |
| mBluetoothLeSuspendedInt = enable; |
| } else { |
| mBluetoothLeSuspendedExt = enable; |
| } |
| updateAudioHalBluetoothState(); |
| } |
| } |
| |
| /*package*/ void clearLeAudioSuspended() { |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "clearLeAudioSuspended"); |
| } |
| synchronized (mBluetoothAudioStateLock) { |
| mBluetoothLeSuspendedInt = false; |
| mBluetoothLeSuspendedExt = false; |
| updateAudioHalBluetoothState(); |
| } |
| } |
| |
| /*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 postSetLeAudioVolumeIndex(int index, int maxIndex, int streamType) { |
| BleVolumeInfo info = new BleVolumeInfo(index, maxIndex, streamType); |
| sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info); |
| } |
| |
| /*package*/ void postSetModeOwner(int mode, int pid, int uid) { |
| sendLMsgNoDelay(MSG_I_SET_MODE_OWNER, SENDMSG_REPLACE, |
| new AudioModeInfo(mode, pid, uid)); |
| } |
| |
| /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) { |
| sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info); |
| } |
| |
| /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, |
| @NonNull String eventSource) { |
| |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "startBluetoothScoForClient, pid: " + pid); |
| } |
| postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( |
| cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), |
| true, scoAudioMode, eventSource, false)); |
| } |
| |
| /*package*/ void stopBluetoothScoForClient( |
| IBinder cb, int pid, @NonNull String eventSource) { |
| |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid); |
| } |
| postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( |
| cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), |
| false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); |
| } |
| |
| /*package*/ int setPreferredDevicesForStrategySync(int strategy, |
| @NonNull List<AudioDeviceAttributes> devices) { |
| return mDeviceInventory.setPreferredDevicesForStrategyAndSave(strategy, devices); |
| } |
| |
| /*package*/ int removePreferredDevicesForStrategySync(int strategy) { |
| return mDeviceInventory.removePreferredDevicesForStrategyAndSave(strategy); |
| } |
| |
| /*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy, |
| @NonNull AudioDeviceAttributes device) { |
| return mDeviceInventory.setDeviceAsNonDefaultForStrategyAndSave(strategy, device); |
| } |
| |
| /*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy, |
| @NonNull AudioDeviceAttributes device) { |
| return mDeviceInventory.removeDeviceAsNonDefaultForStrategyAndSave(strategy, device); |
| } |
| |
| /*package*/ void registerStrategyPreferredDevicesDispatcher( |
| @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { |
| mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher); |
| } |
| |
| /*package*/ void unregisterStrategyPreferredDevicesDispatcher( |
| @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { |
| mDeviceInventory.unregisterStrategyPreferredDevicesDispatcher(dispatcher); |
| } |
| |
| /*package*/ void registerStrategyNonDefaultDevicesDispatcher( |
| @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { |
| mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher); |
| } |
| |
| /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( |
| @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { |
| mDeviceInventory.unregisterStrategyNonDefaultDevicesDispatcher(dispatcher); |
| } |
| |
| /*package*/ int setPreferredDevicesForCapturePresetSync(int capturePreset, |
| @NonNull List<AudioDeviceAttributes> devices) { |
| return mDeviceInventory.setPreferredDevicesForCapturePresetAndSave(capturePreset, devices); |
| } |
| |
| /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) { |
| return mDeviceInventory.clearPreferredDevicesForCapturePresetAndSave(capturePreset); |
| } |
| |
| /*package*/ void registerCapturePresetDevicesRoleDispatcher( |
| @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { |
| mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher); |
| } |
| |
| /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( |
| @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { |
| mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); |
| } |
| |
| /*package*/ void registerCommunicationDeviceDispatcher( |
| @NonNull ICommunicationDeviceDispatcher dispatcher) { |
| mCommDevDispatchers.register(dispatcher); |
| } |
| |
| /*package*/ void unregisterCommunicationDeviceDispatcher( |
| @NonNull ICommunicationDeviceDispatcher dispatcher) { |
| mCommDevDispatchers.unregister(dispatcher); |
| } |
| |
| // Monitoring of communication device |
| final RemoteCallbackList<ICommunicationDeviceDispatcher> mCommDevDispatchers = |
| new RemoteCallbackList<ICommunicationDeviceDispatcher>(); |
| |
| // portId of the device currently selected for communication: avoids broadcasting changes |
| // when same communication route is applied |
| @GuardedBy("mDeviceStateLock") |
| int mCurCommunicationPortId = -1; |
| |
| @GuardedBy("mDeviceStateLock") |
| private void dispatchCommunicationDevice() { |
| AudioDeviceInfo device = getCommunicationDevice(); |
| int portId = device != null ? device.getId() : 0; |
| if (portId == mCurCommunicationPortId) { |
| return; |
| } |
| mCurCommunicationPortId = portId; |
| |
| final int nbDispatchers = mCommDevDispatchers.beginBroadcast(); |
| for (int i = 0; i < nbDispatchers; i++) { |
| try { |
| mCommDevDispatchers.getBroadcastItem(i) |
| .dispatchCommunicationDeviceChanged(portId); |
| } catch (RemoteException e) { |
| } |
| } |
| mCommDevDispatchers.finishBroadcast(); |
| } |
| |
| |
| //--------------------------------------------------------------------- |
| // 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 getMaxVssVolumeForStream(int streamType) { |
| return mAudioService.getMaxVssVolumeForStream(streamType); |
| } |
| |
| /*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. |
| // Each of these methods posts a message to mBrokerHandler message queue. |
| /*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); |
| } |
| |
| @GuardedBy("mDeviceStateLock") |
| /*package*/ void postBluetoothActiveDevice(BtDeviceInfo info, int delay) { |
| sendLMsg(MSG_L_SET_BT_ACTIVE_DEVICE, SENDMSG_QUEUE, info, delay); |
| } |
| |
| /*package*/ void postSetWiredDeviceConnectionState( |
| AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) { |
| sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay); |
| } |
| |
| /*package*/ void postBtProfileDisconnected(int profile) { |
| sendIMsgNoDelay(MSG_I_BT_SERVICE_DISCONNECTED_PROFILE, SENDMSG_QUEUE, profile); |
| } |
| |
| /*package*/ void postBtProfileConnected(int profile, BluetoothProfile proxy) { |
| sendILMsgNoDelay(MSG_IL_BT_SERVICE_CONNECTED_PROFILE, SENDMSG_QUEUE, profile, proxy); |
| } |
| |
| /*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) { |
| sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client); |
| } |
| |
| /*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy, |
| List<AudioDeviceAttributes> devices) |
| { |
| sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy, devices); |
| } |
| |
| /*package*/ void postSaveRemovePreferredDevicesForStrategy(int strategy) { |
| sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy); |
| } |
| |
| /*package*/ void postSaveSetDeviceAsNonDefaultForStrategy( |
| int strategy, AudioDeviceAttributes device) { |
| sendILMsgNoDelay(MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device); |
| } |
| |
| /*package*/ void postSaveRemoveDeviceAsNonDefaultForStrategy( |
| int strategy, AudioDeviceAttributes device) { |
| sendILMsgNoDelay( |
| MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device); |
| } |
| |
| /*package*/ void postSaveSetPreferredDevicesForCapturePreset( |
| int capturePreset, List<AudioDeviceAttributes> devices) { |
| sendILMsgNoDelay( |
| MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset, devices); |
| } |
| |
| /*package*/ void postSaveClearPreferredDevicesForCapturePreset(int capturePreset) { |
| sendIMsgNoDelay( |
| MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset); |
| } |
| |
| /*package*/ void postUpdateCommunicationRouteClient(String eventSource) { |
| sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource); |
| } |
| |
| /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) { |
| sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info); |
| } |
| |
| /*package*/ void postScoAudioStateChanged(int state) { |
| sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state); |
| } |
| |
| /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) { |
| sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice); |
| } |
| |
| /*package*/ static final class CommunicationDeviceInfo { |
| final @NonNull IBinder mCb; // Identifies the requesting client for death handler |
| final int mPid; // Requester process ID |
| final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset. |
| final boolean mOn; // true if setting, false if resetting |
| final int mScoAudioMode; // only used for SCO: requested audio mode |
| final @NonNull String mEventSource; // caller identifier for logging |
| boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent) |
| boolean mStatus = false; // completion status only used if mWaitForStatus is true |
| |
| CommunicationDeviceInfo(@NonNull IBinder cb, int pid, |
| @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode, |
| @NonNull String eventSource, boolean waitForStatus) { |
| mCb = cb; |
| mPid = pid; |
| mDevice = device; |
| mOn = on; |
| mScoAudioMode = scoAudioMode; |
| mEventSource = eventSource; |
| mWaitForStatus = waitForStatus; |
| } |
| |
| // redefine equality op so we can match messages intended for this client |
| @Override |
| public boolean equals(Object o) { |
| if (o == null) { |
| return false; |
| } |
| if (this == o) { |
| return true; |
| } |
| if (!(o instanceof CommunicationDeviceInfo)) { |
| return false; |
| } |
| |
| return mCb.equals(((CommunicationDeviceInfo) o).mCb) |
| && mPid == ((CommunicationDeviceInfo) o).mPid; |
| } |
| |
| @Override |
| public String toString() { |
| return "CommunicationDeviceInfo mCb=" + mCb.toString() |
| + " mPid=" + mPid |
| + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]" |
| + " mOn=" + mOn |
| + " mScoAudioMode=" + mScoAudioMode |
| + " mEventSource=" + mEventSource |
| + " mWaitForStatus=" + mWaitForStatus |
| + " mStatus=" + mStatus; |
| } |
| } |
| |
| //--------------------------------------------------------------------- |
| // 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, boolean fromA2dp, 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, |
| fromA2dp, |
| eventSource); |
| } |
| } |
| |
| /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, |
| boolean connect, @Nullable BluetoothDevice btDevice) { |
| synchronized (mDeviceStateLock) { |
| return mDeviceInventory.handleDeviceConnection( |
| attributes, connect, false /*for test*/, btDevice); |
| } |
| } |
| |
| /*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(boolean fromA2dp) { |
| sendMsgNoDelay(fromA2dp ? MSG_REPORT_NEW_ROUTES_A2DP : MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP); |
| } |
| |
| // must be called synchronized on mConnectedDevices |
| /*package*/ boolean hasScheduledA2dpConnection(BluetoothDevice btDevice) { |
| final BtDeviceInfo devInfoToCheck = new BtDeviceInfo(btDevice, BluetoothProfile.A2DP); |
| return mBrokerHandler.hasEqualMessages(MSG_L_SET_BT_ACTIVE_DEVICE, devInfoToCheck); |
| } |
| |
| /*package*/ void setA2dpTimeout(String address, int a2dpCodec, int delayMs) { |
| sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); |
| } |
| |
| /*package*/ void setLeAudioTimeout(String address, int device, int delayMs) { |
| sendILMsg(MSG_IL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, address, delayMs); |
| } |
| |
| /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); |
| } |
| } |
| |
| /*package*/ void clearAvrcpAbsoluteVolumeSupported() { |
| setAvrcpAbsoluteVolumeSupported(false); |
| mAudioService.setAvrcpAbsoluteVolumeSupported(false); |
| } |
| |
| /*package*/ boolean getBluetoothA2dpEnabled() { |
| synchronized (mDeviceStateLock) { |
| return mBluetoothA2dpEnabled; |
| } |
| } |
| |
| /*package*/ void broadcastStickyIntentToCurrentProfileGroup(Intent intent) { |
| mSystemServer.broadcastStickyIntentToCurrentProfileGroup(intent); |
| } |
| |
| /*package*/ void dump(PrintWriter pw, String prefix) { |
| if (mBrokerHandler != null) { |
| pw.println(prefix + "Message handler (watch for unhandled messages):"); |
| mBrokerHandler.dump(new PrintWriterPrinter(pw), prefix + " "); |
| } else { |
| pw.println("Message handler is null"); |
| } |
| |
| mDeviceInventory.dump(pw, prefix); |
| |
| pw.println("\n" + prefix + "Communication route clients:"); |
| mCommunicationRouteClients.forEach((cl) -> { |
| pw.println(" " + prefix + "pid: " + cl.getPid() + " device: " |
| + cl.getDevice() + " cb: " + cl.getBinder()); }); |
| |
| pw.println("\n" + prefix + "Computed Preferred communication device: " |
| + preferredCommunicationDevice()); |
| pw.println("\n" + prefix + "Applied Preferred communication device: " |
| + mPreferredCommunicationDevice); |
| pw.println(prefix + "Active communication device: " |
| + ((mActiveCommunicationDevice == null) ? "None" |
| : new AudioDeviceAttributes(mActiveCommunicationDevice))); |
| |
| pw.println(prefix + "mCommunicationStrategyId: " |
| + mCommunicationStrategyId); |
| |
| pw.println(prefix + "mAccessibilityStrategyId: " |
| + mAccessibilityStrategyId); |
| |
| pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner); |
| |
| mBtHelper.dump(pw, prefix); |
| } |
| |
| //--------------------------------------------------------------------- |
| // 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, boolean fromA2dp, String eventSource) { |
| if (useCase == AudioSystem.FOR_MEDIA) { |
| postReportNewRoutes(fromA2dp); |
| } |
| AudioService.sForceUseLogger.enqueue( |
| new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); |
| new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE + MediaMetrics.SEPARATOR |
| + AudioSystem.forceUseUsageToString(useCase)) |
| .set(MediaMetrics.Property.EVENT, "onSetForceUse") |
| .set(MediaMetrics.Property.FORCE_USE_DUE_TO, eventSource) |
| .set(MediaMetrics.Property.FORCE_USE_MODE, |
| AudioSystem.forceUseConfigToString(config)) |
| .record(); |
| |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<" |
| + fromA2dp + ">, eventSource<" + eventSource + ">)"); |
| } |
| mAudioSystem.setForceUse(useCase, config); |
| } |
| |
| private void onSendBecomingNoisyIntent() { |
| AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( |
| "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); |
| mSystemServer.sendDeviceBecomingNoisyIntent(); |
| } |
| |
| //--------------------------------------------------------------------- |
| // 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 (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| initRoutingStrategyIds(); |
| updateActiveCommunicationDevice(); |
| mDeviceInventory.onRestoreDevices(); |
| mBtHelper.onAudioServerDiedRestoreA2dp(); |
| updateCommunicationRoute("MSG_RESTORE_DEVICES"); |
| } |
| } |
| 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, |
| (msg.what == MSG_IIL_SET_FORCE_BT_A2DP_USE), (String) msg.obj); |
| break; |
| case MSG_REPORT_NEW_ROUTES: |
| case MSG_REPORT_NEW_ROUTES_A2DP: |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.onReportNewRoutes(); |
| } |
| break; |
| case MSG_L_SET_BT_ACTIVE_DEVICE: |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; |
| mDeviceInventory.onSetBtActiveDevice(btInfo, |
| (btInfo.mProfile |
| != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput) |
| ? mAudioService.getBluetoothContextualVolumeStream() |
| : AudioSystem.STREAM_DEFAULT); |
| if (btInfo.mProfile == BluetoothProfile.LE_AUDIO |
| || btInfo.mProfile == BluetoothProfile.HEARING_AID) { |
| onUpdateCommunicationRouteClient("setBluetoothActiveDevice"); |
| } |
| } |
| } |
| break; |
| case MSG_BT_HEADSET_CNCT_FAILED: |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.resetBluetoothSco(); |
| } |
| } |
| break; |
| case MSG_IL_BTA2DP_TIMEOUT: |
| // msg.obj == address of BTA2DP device |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); |
| } |
| break; |
| case MSG_IL_BTLEAUDIO_TIMEOUT: |
| // msg.obj == address of LE Audio device |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.onMakeLeAudioDeviceUnavailableNow( |
| (String) msg.obj, msg.arg1); |
| } |
| break; |
| case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.onBluetoothDeviceConfigChange( |
| (BtDeviceInfo) msg.obj, 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, |
| mDeviceInventory.isHearingAidConnected()); |
| } |
| break; |
| case MSG_II_SET_LE_AUDIO_OUT_VOLUME: { |
| final BleVolumeInfo info = (BleVolumeInfo) msg.obj; |
| synchronized (mDeviceStateLock) { |
| mBtHelper.setLeAudioVolume(info.mIndex, info.mMaxIndex, info.mStreamType); |
| } |
| } break; |
| case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME: |
| synchronized (mDeviceStateLock) { |
| mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); |
| } |
| break; |
| case MSG_I_SET_MODE_OWNER: |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| mAudioModeOwner = (AudioModeInfo) msg.obj; |
| if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) { |
| onUpdateCommunicationRouteClient("setNewModeOwner"); |
| } |
| } |
| } |
| break; |
| |
| case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT: |
| CommunicationDeviceInfo deviceInfo = (CommunicationDeviceInfo) msg.obj; |
| boolean status; |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| status = onSetCommunicationDeviceForClient(deviceInfo); |
| } |
| } |
| synchronized (deviceInfo) { |
| if (deviceInfo.mWaitForStatus) { |
| deviceInfo.mStatus = status; |
| deviceInfo.mWaitForStatus = false; |
| deviceInfo.notify(); |
| } |
| } |
| break; |
| |
| case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT: |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| onUpdateCommunicationRouteClient((String) msg.obj); |
| } |
| } |
| break; |
| |
| case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED: |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj); |
| } |
| } |
| break; |
| |
| case MSG_I_SCO_AUDIO_STATE_CHANGED: |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.onScoAudioStateChanged(msg.arg1); |
| } |
| } |
| break; |
| |
| case MSG_TOGGLE_HDMI: |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.onToggleHdmi(); |
| } |
| break; |
| case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE: |
| if (msg.arg1 != BluetoothProfile.HEADSET) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.onBtProfileDisconnected(msg.arg1); |
| mDeviceInventory.onBtProfileDisconnected(msg.arg1); |
| } |
| } else { |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.disconnectHeadset(); |
| } |
| } |
| } |
| break; |
| case MSG_IL_BT_SERVICE_CONNECTED_PROFILE: |
| if (msg.arg1 != BluetoothProfile.HEADSET) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.onBtProfileConnected(msg.arg1, (BluetoothProfile) msg.obj); |
| } |
| } else { |
| synchronized (mSetModeLock) { |
| synchronized (mDeviceStateLock) { |
| mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj); |
| } |
| } |
| } |
| break; |
| case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: { |
| final BtDeviceInfo info = (BtDeviceInfo) msg.obj; |
| if (info.mDevice == null) break; |
| AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( |
| "msg: onBluetoothActiveDeviceChange " |
| + " 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 |
| + " src=" + info.mEventSource |
| )).printLog(TAG)); |
| synchronized (mDeviceStateLock) { |
| mDeviceInventory.setBluetoothActiveDevice(info); |
| } |
| } break; |
| case MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY: { |
| final int strategy = msg.arg1; |
| final List<AudioDeviceAttributes> devices = |
| (List<AudioDeviceAttributes>) msg.obj; |
| mDeviceInventory.onSaveSetPreferredDevices(strategy, devices); |
| } break; |
| case MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY: { |
| final int strategy = msg.arg1; |
| mDeviceInventory.onSaveRemovePreferredDevices(strategy); |
| } break; |
| case MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY: { |
| final int strategy = msg.arg1; |
| final AudioDeviceAttributes device = (AudioDeviceAttributes) msg.obj; |
| mDeviceInventory.onSaveSetDeviceAsNonDefault(strategy, device); |
| } break; |
| case MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY: { |
| final int strategy = msg.arg1; |
| final AudioDeviceAttributes device = (AudioDeviceAttributes) msg.obj; |
| mDeviceInventory.onSaveRemoveDeviceAsNonDefault(strategy, device); |
| } break; |
| case MSG_CHECK_MUTE_MUSIC: |
| checkMessagesMuteMusic(0); |
| break; |
| case MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET: { |
| final int capturePreset = msg.arg1; |
| final List<AudioDeviceAttributes> devices = |
| (List<AudioDeviceAttributes>) msg.obj; |
| mDeviceInventory.onSaveSetPreferredDevicesForCapturePreset( |
| capturePreset, devices); |
| } break; |
| case MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET: { |
| final int capturePreset = msg.arg1; |
| mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); |
| } break; |
| case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: { |
| final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; |
| BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); |
| } break; |
| default: |
| Log.wtf(TAG, "Invalid message " + msg.what); |
| } |
| |
| // Give some time to Bluetooth service to post a connection message |
| // in case of active device switch |
| if (MESSAGES_MUTE_MUSIC.contains(msg.what)) { |
| sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS); |
| } |
| |
| 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_TOGGLE_HDMI = 6; |
| private static final int MSG_L_SET_BT_ACTIVE_DEVICE = 7; |
| private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; |
| private static final int MSG_IL_BTA2DP_TIMEOUT = 10; |
| |
| // process change of A2DP device configuration, obj is BluetoothDevice |
| private static final int MSG_L_BLUETOOTH_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_SET_MODE_OWNER = 16; |
| |
| private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22; |
| private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23; |
| |
| // process external command to (dis)connect an A2DP device, obj is BtDeviceConnectionInfo |
| private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 29; |
| |
| // process external command to (dis)connect a hearing aid device |
| private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31; |
| |
| private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 32; |
| private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 33; |
| |
| private static final int MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED = 34; |
| private static final int MSG_CHECK_MUTE_MUSIC = 35; |
| private static final int MSG_REPORT_NEW_ROUTES_A2DP = 36; |
| |
| private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37; |
| private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38; |
| |
| private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42; |
| private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43; |
| private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44; |
| |
| private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45; |
| // |
| // process set volume for Le Audio, obj is BleVolumeInfo |
| private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46; |
| |
| private static final int MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY = 47; |
| private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48; |
| private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; |
| |
| private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52; |
| |
| private static boolean isMessageHandledUnderWakelock(int msgId) { |
| switch(msgId) { |
| case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: |
| case MSG_L_SET_BT_ACTIVE_DEVICE: |
| case MSG_IL_BTA2DP_TIMEOUT: |
| case MSG_IL_BTLEAUDIO_TIMEOUT: |
| case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: |
| case MSG_TOGGLE_HDMI: |
| case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: |
| case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: |
| case MSG_CHECK_MUTE_MUSIC: |
| 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); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| if (MESSAGES_MUTE_MUSIC.contains(msg)) { |
| checkMessagesMuteMusic(msg); |
| } |
| |
| synchronized (sLastDeviceConnectionMsgTimeLock) { |
| long time = SystemClock.uptimeMillis() + delay; |
| |
| switch (msg) { |
| case MSG_L_SET_BT_ACTIVE_DEVICE: |
| case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: |
| case MSG_IL_BTA2DP_TIMEOUT: |
| case MSG_IL_BTLEAUDIO_TIMEOUT: |
| case MSG_L_BLUETOOTH_DEVICE_CONFIG_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); |
| } |
| } |
| |
| /** List of messages for which music is muted while processing is pending */ |
| private static final Set<Integer> MESSAGES_MUTE_MUSIC; |
| static { |
| MESSAGES_MUTE_MUSIC = new HashSet<>(); |
| MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE); |
| MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE); |
| MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT); |
| MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE); |
| } |
| |
| private AtomicBoolean mMusicMuted = new AtomicBoolean(false); |
| |
| private static <T> boolean hasIntersection(Set<T> a, Set<T> b) { |
| for (T e : a) { |
| if (b.contains(e)) return true; |
| } |
| return false; |
| } |
| |
| boolean messageMutesMusic(int message) { |
| if (message == 0) { |
| return false; |
| } |
| // Do not mute on bluetooth event if music is playing on a wired headset. |
| if ((message == MSG_L_SET_BT_ACTIVE_DEVICE |
| || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT |
| || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE) |
| && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) |
| && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET, |
| mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** Mutes or unmutes music according to pending A2DP messages */ |
| private void checkMessagesMuteMusic(int message) { |
| boolean mute = messageMutesMusic(message); |
| if (!mute) { |
| for (int msg : MESSAGES_MUTE_MUSIC) { |
| if (mBrokerHandler.hasMessages(msg)) { |
| if (messageMutesMusic(msg)) { |
| mute = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (mute != mMusicMuted.getAndSet(mute)) { |
| mAudioService.setMusicMute(mute); |
| } |
| } |
| |
| // List of applications requesting a specific route for communication. |
| @GuardedBy("mDeviceStateLock") |
| private final @NonNull LinkedList<CommunicationRouteClient> mCommunicationRouteClients = |
| new LinkedList<CommunicationRouteClient>(); |
| |
| private class CommunicationRouteClient implements IBinder.DeathRecipient { |
| private final IBinder mCb; |
| private final int mPid; |
| private AudioDeviceAttributes mDevice; |
| |
| CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) { |
| mCb = cb; |
| mPid = pid; |
| mDevice = device; |
| } |
| |
| public boolean registerDeathRecipient() { |
| boolean status = false; |
| try { |
| mCb.linkToDeath(this, 0); |
| status = true; |
| } catch (RemoteException e) { |
| Log.w(TAG, "CommunicationRouteClient could not link to " + mCb + " binder death"); |
| } |
| return status; |
| } |
| |
| public void unregisterDeathRecipient() { |
| try { |
| mCb.unlinkToDeath(this, 0); |
| } catch (NoSuchElementException e) { |
| Log.w(TAG, "CommunicationRouteClient could not not unregistered to binder"); |
| } |
| } |
| |
| @Override |
| public void binderDied() { |
| postCommunicationRouteClientDied(this); |
| } |
| |
| IBinder getBinder() { |
| return mCb; |
| } |
| |
| int getPid() { |
| return mPid; |
| } |
| |
| AudioDeviceAttributes getDevice() { |
| return mDevice; |
| } |
| } |
| |
| // @GuardedBy("mSetModeLock") |
| @GuardedBy("mDeviceStateLock") |
| private void onCommunicationRouteClientDied(CommunicationRouteClient client) { |
| if (client == null) { |
| return; |
| } |
| Log.w(TAG, "Communication client died"); |
| setCommunicationRouteForClient(client.getBinder(), client.getPid(), null, |
| BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied"); |
| } |
| |
| /** |
| * Determines which preferred device for phone strategy should be sent to audio policy manager |
| * as a function of current SCO audio activation state and active communication route requests. |
| * SCO audio state has the highest priority as it can result from external activation by |
| * telephony service. |
| * @return selected forced usage for communication. |
| */ |
| @GuardedBy("mDeviceStateLock") |
| @Nullable private AudioDeviceAttributes preferredCommunicationDevice() { |
| boolean btSCoOn = mBtHelper.isBluetoothScoOn(); |
| synchronized (mBluetoothAudioStateLock) { |
| btSCoOn = btSCoOn && mBluetoothScoOn; |
| } |
| |
| if (btSCoOn) { |
| // Use the SCO device known to BtHelper so that it matches exactly |
| // what has been communicated to audio policy manager. The device |
| // returned by requestedCommunicationDevice() can be a dummy SCO device if legacy |
| // APIs are used to start SCO audio. |
| AudioDeviceAttributes device = mBtHelper.getHeadsetAudioDevice(); |
| if (device != null) { |
| return device; |
| } |
| } |
| AudioDeviceAttributes device = requestedCommunicationDevice(); |
| if (device == null || device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) { |
| // Do not indicate BT SCO selection if SCO is requested but SCO is not ON |
| return null; |
| } |
| return device; |
| } |
| |
| /** |
| * Configures audio policy manager and audio HAL according to active communication route. |
| * Always called from message Handler. |
| */ |
| // @GuardedBy("mSetModeLock") |
| @GuardedBy("mDeviceStateLock") |
| private void updateCommunicationRoute(String eventSource) { |
| AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice(); |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: " |
| + preferredCommunicationDevice + " eventSource: " + eventSource); |
| } |
| AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( |
| "updateCommunicationRoute, preferredCommunicationDevice: " |
| + preferredCommunicationDevice + " eventSource: " + eventSource))); |
| |
| if (preferredCommunicationDevice == null) { |
| AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice(); |
| if (defaultDevice != null) { |
| mDeviceInventory.setPreferredDevicesForStrategy( |
| mCommunicationStrategyId, Arrays.asList(defaultDevice)); |
| mDeviceInventory.setPreferredDevicesForStrategy( |
| mAccessibilityStrategyId, Arrays.asList(defaultDevice)); |
| } else { |
| mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId); |
| mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId); |
| } |
| mDeviceInventory.applyConnectedDevicesRoles(); |
| } else { |
| mDeviceInventory.setPreferredDevicesForStrategy( |
| mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice)); |
| mDeviceInventory.setPreferredDevicesForStrategy( |
| mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice)); |
| } |
| onUpdatePhoneStrategyDevice(preferredCommunicationDevice); |
| } |
| |
| /** |
| * Select new communication device from communication route client at the top of the stack |
| * and restore communication route including restarting SCO audio if needed. |
| */ |
| // @GuardedBy("mSetModeLock") |
| @GuardedBy("mDeviceStateLock") |
| private void onUpdateCommunicationRouteClient(String eventSource) { |
| updateCommunicationRoute(eventSource); |
| CommunicationRouteClient crc = topCommunicationRouteClient(); |
| if (AudioService.DEBUG_COMM_RTE) { |
| Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " |
| + crc + " eventSource: " + eventSource); |
| } |
| if (crc != null) { |
| setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(), |
| BtHelper.SCO_MODE_UNDEFINED, eventSource); |
| } |
| } |
| |
| // @GuardedBy("mSetModeLock") |
| @GuardedBy("mDeviceStateLock") |
| private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) { |
| boolean wasSpeakerphoneActive = isSpeakerphoneActive(); |
| mPreferredCommunicationDevice = device; |
| updateActiveCommunicationDevice(); |
| if (wasSpeakerphoneActive != isSpeakerphoneActive()) { |
| try { |
| mContext.sendBroadcastAsUser( |
| new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) |
| .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), |
| UserHandle.ALL); |
| } catch (Exception e) { |
| Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e); |
| } |
| } |
| mAudioService.postUpdateRingerModeServiceInt(); |
| dispatchCommunicationDevice(); |
| } |
| |
| private CommunicationRouteClient removeCommunicationRouteClient( |
| IBinder cb, boolean unregister) { |
| for (CommunicationRouteClient cl : mCommunicationRouteClients) { |
| if (cl.getBinder() == cb) { |
| if (unregister) { |
| cl.unregisterDeathRecipient(); |
| } |
| mCommunicationRouteClients.remove(cl); |
| return cl; |
| } |
| } |
| return null; |
| } |
| |
| @GuardedBy("mDeviceStateLock") |
| private CommunicationRouteClient addCommunicationRouteClient( |
| IBinder cb, int pid, AudioDeviceAttributes device) { |
| // always insert new request at first position |
| removeCommunicationRouteClient(cb, true); |
| CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device); |
| if (client.registerDeathRecipient()) { |
| mCommunicationRouteClients.add(0, client); |
| return client; |
| } |
| return null; |
| } |
| |
| @GuardedBy("mDeviceStateLock") |
| private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) { |
| for (CommunicationRouteClient cl : mCommunicationRouteClients) { |
| if (cl.getPid() == pid) { |
| return cl; |
| } |
| } |
| return null; |
| } |
| |
| @GuardedBy("mDeviceStateLock") |
| private boolean communnicationDeviceCompatOn() { |
| return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION |
| && !(CompatChanges.isChangeEnabled( |
| USE_SET_COMMUNICATION_DEVICE, mAudioModeOwner.mUid) |
| || mAudioModeOwner.mUid == android.os.Process.SYSTEM_UID); |
| } |
| |
| @GuardedBy("mDeviceStateLock") |
| AudioDeviceAttributes getDefaultCommunicationDevice() { |
| // For system server (Telecom) and APKs targeting S and above, we let the audio |
| // policy routing rules select the default communication device. |
| // For older APKs, we force Hearing Aid or LE Audio headset when connected as |
| // those APKs cannot select a LE Audio or Hearing Aid device explicitly. |
| AudioDeviceAttributes device = null; |
| if (communnicationDeviceCompatOn()) { |
| // If both LE and Hearing Aid are active (thie should not happen), |
| // priority to Hearing Aid. |
| device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_HEARING_AID); |
| if (device == null) { |
| device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET); |
| } |
| } |
| return device; |
| } |
| |
| @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { |
| synchronized (mDeviceStateLock) { |
| return mDeviceInventory.getDeviceSensorUuid(device); |
| } |
| } |
| |
| void dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(AudioDeviceInfo info) { |
| // Currently, only media usage will be allowed to set preferred mixer attributes |
| mAudioService.dispatchPreferredMixerAttributesChanged( |
| new AudioAttributes.Builder() |
| .setUsage(AudioAttributes.USAGE_MEDIA).build(), |
| info.getId(), |
| null /*mixerAttributes*/); |
| } |
| } |