From ec51aa8207d0ecf0de270812870de01bfc8f3588 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Thu, 24 Sep 2020 09:47:37 -0700 Subject: [PATCH 1/3] AudioService: refactor communication route control Audio routing for communication use cases (cell, video and VoIP calls) is controlled by separate APIs: setSpeakerPhoneOn(), startBluetoothSco() and stopBluetoothSco(). Requests for speakerphone and bluetooth are managed by separate client stacks although in the end they control a single routing state in audio policy manager, which creates problems in case of conflicting requests and race conditions. This CL refactors the implementation by regrouping the Bluetooth Sco and Speaker client stacks into a single one. This makes sure that no conflicting routing request exist for a given client. It also makes handling of race conditions and prioritization between clients easier. Finally, it prepares the introduction of a new API that will be the single entry point for communication route control. Also in this CL: - Option to enable more debug log for communication route - Fixes in BtHelper.receiveBtEvent() 1) Fix intent broadcast missing in case of transition from external activation to internal activation. 2) Do not clear SCO requests when SCO audio is disconnected: this can happen because current active communication route request is different from SCO but pending requests must stay in the stack. 3) Do not clear SCO requests when the audio mode owner changes. Any Active communication route requested by new audio mode owner will be honored and pending requests will be restored when mode owner changes again. Test: regression tests with cell and VoIP calls Change-Id: If9d2f24b9def78ccd791294ed42d95ce30f0ba8b Merged-In: If9d2f24b9def78ccd791294ed42d95ce30f0ba8b --- .../server/audio/AudioDeviceBroker.java | 493 +++++++++++------- .../android/server/audio/AudioService.java | 49 +- .../com/android/server/audio/BtHelper.java | 444 +++++----------- 3 files changed, 465 insertions(+), 521 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 1615998f77875..3b407f14297ea 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -25,6 +25,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioRoutesObserver; @@ -39,6 +41,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.PrintWriterPrinter; @@ -46,8 +49,8 @@ import android.util.PrintWriterPrinter; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; @@ -73,10 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean; /** 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; @@ -136,7 +135,6 @@ import java.util.concurrent.atomic.AtomicBoolean; setupMessaging(mContext); mForcedUseForComm = AudioSystem.FORCE_NONE; - mForcedUseForCommExt = mForcedUseForComm; } /*package*/ Context getContext() { @@ -159,15 +157,6 @@ import java.util.concurrent.atomic.AtomicBoolean; } /*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, - false /*fromA2dp*/, "onAudioServerDied"); - onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, - false /*fromA2dp*/, "onAudioServerDied"); - } // restore devices sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE); } @@ -219,85 +208,156 @@ import java.util.concurrent.atomic.AtomicBoolean; * Turns speakerphone on/off * @param on * @param eventSource for logging purposes - * @return true if speakerphone state changed */ - /*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { - synchronized (mDeviceStateLock) { - if (!addSpeakerphoneClient(cb, pid, on)) { - return false; + /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid); + } + + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + AudioDeviceAttributes device = null; + if (on) { + device = new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + } else { + CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); + if (client == null || !client.requestsSpeakerphone()) { + return; + } + } + setCommunicationRouteForClient( + cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource); } - if (on) { - // Cancel BT SCO ON request by this same client: speakerphone and BT SCO routes - // are mutually exclusive. - // See symmetrical operation for startBluetoothScoForClient_Sync(). - mBtHelper.stopBluetoothScoForPid(pid); - } - final boolean wasOn = isSpeakerphoneOn(); - updateSpeakerphoneOn(eventSource); - return (wasOn != isSpeakerphoneOn()); } } - /** - * Turns speakerphone off for a given pid and update speakerphone state. - * @param pid - */ @GuardedBy("mDeviceStateLock") - private void setSpeakerphoneOffForPid(int pid) { - SpeakerphoneClient client = getSpeakerphoneClientForPid(pid); + /*package*/ void setCommunicationRouteForClient( + IBinder cb, int pid, AudioDeviceAttributes device, + int scoAudioMode, String eventSource) { + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "setCommunicationRouteForClient: device: " + device); + } + + final boolean wasBtScoRequested = isBluetoothScoRequested(); + final boolean wasSpeakerphoneRequested = isSpeakerphoneRequested(); + 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; } - client.unregisterDeathRecipient(); - mSpeakerphoneClients.remove(client); - final String eventSource = new StringBuilder("setSpeakerphoneOffForPid(") - .append(pid).append(")").toString(); - updateSpeakerphoneOn(eventSource); - } - @GuardedBy("mDeviceStateLock") - private void updateSpeakerphoneOn(String eventSource) { - if (isSpeakerphoneOnRequested()) { - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + boolean isBtScoRequested = isBluetoothScoRequested(); + if (isBtScoRequested && !wasBtScoRequested) { + 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); + } } - mForcedUseForComm = AudioSystem.FORCE_SPEAKER; - } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { - if (mBtHelper.isBluetoothScoOn()) { - mForcedUseForComm = AudioSystem.FORCE_BT_SCO; - setForceUse_Async( - AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource); - } else { - mForcedUseForComm = AudioSystem.FORCE_NONE; + } else if (!isBtScoRequested && wasBtScoRequested) { + mBtHelper.stopBluetoothSco(eventSource); + } + + if (wasSpeakerphoneRequested != isSpeakerphoneRequested()) { + 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); } } - mForcedUseForCommExt = mForcedUseForComm; - setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + + sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource); } /** - * Returns if speakerphone is requested ON or OFF. - * If the current audio mode owner is in the speakerphone client list, use this preference. + * Returns the device currently requested for communication use case. + * If the current audio mode owner is in the communication route client list, + * use this preference. * Otherwise use first client's preference (first client corresponds to latest request). - * Speakerphone is requested OFF if no client is in the list. - * @return true if speakerphone is requested ON, false otherwise + * null is returned if no client is in the list. + * @return AudioDeviceAttributes the requested device for communication. */ + @GuardedBy("mDeviceStateLock") - private boolean isSpeakerphoneOnRequested() { - if (mSpeakerphoneClients.isEmpty()) { - return false; - } - for (SpeakerphoneClient cl : mSpeakerphoneClients) { + private AudioDeviceAttributes requestedCommunicationDevice() { + AudioDeviceAttributes device = null; + for (CommunicationRouteClient cl : mCommunicationRouteClients) { if (cl.getPid() == mModeOwnerPid) { - return cl.isOn(); + device = cl.getDevice(); } } - return mSpeakerphoneClients.get(0).isOn(); + if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) { + device = mCommunicationRouteClients.get(0).getDevice(); + } + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "requestedCommunicationDevice, device: " + + device + " mode owner pid: " + mModeOwnerPid); + } + return device; } - /*package*/ boolean isSpeakerphoneOn() { + /** + * Helper method on top of requestedCommunicationDevice() indicating if + * speakerphone ON is currently requested or not. + * @return true if speakerphone ON requested, false otherwise. + */ + + private boolean isSpeakerphoneRequested() { synchronized (mDeviceStateLock) { - return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + AudioDeviceAttributes device = requestedCommunicationDevice(); + return device != null + && device.getType() + == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + } + } + + /** + * Indicates if active route selection for communication is speakerphone. + * @return true if speakerphone is active, false otherwise. + */ + /*package*/ boolean isSpeakerphoneOn() { + return getForcedUseForComm() == AudioSystem.FORCE_SPEAKER; + } + + /** + * Helper method on top of requestedCommunicationDevice() indicating if + * Bluetooth SCO ON is currently requested or not. + * @return true if Bluetooth SCO ON is requested, false otherwise. + */ + /*package*/ boolean isBluetoothScoRequested() { + synchronized (mDeviceStateLock) { + AudioDeviceAttributes device = requestedCommunicationDevice(); + return device != null + && device.getType() + == AudioDeviceInfo.TYPE_BLUETOOTH_SCO; } } @@ -348,7 +408,6 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { @@ -431,42 +490,28 @@ import java.util.concurrent.atomic.AtomicBoolean; 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; - } - } + /** + * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn(). + */ + private boolean mBluetoothScoOn; /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { - //Log.i(TAG, "setBluetoothScoOn: " + 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 = isSpeakerphoneOnRequested() - ? AudioSystem.FORCE_SPEAKER : 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); + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource); } - // Un-mute ringtone stream volume - mAudioService.postUpdateRingerModeServiceInt(); + synchronized (mDeviceStateLock) { + mBluetoothScoOn = on; + sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource); + } + } + + /** + * Indicates if active route selection for communication is Bluetooth SCO. + * @return true if Bluetooth SCO is active , false otherwise. + */ + /*package*/ boolean isBluetoothScoOn() { + return getForcedUseForComm() == AudioSystem.FORCE_BT_SCO; } /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { @@ -509,22 +554,43 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); } - @GuardedBy("mSetModeLock") - /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode, + /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) { - synchronized (mDeviceStateLock) { - // Cancel speakerphone ON request by this same client: speakerphone and BT SCO routes - // are mutually exclusive. - // See symmetrical operation for setSpeakerphoneOn(true). - setSpeakerphoneOffForPid(Binder.getCallingPid()); - mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource); + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid); + } + + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + AudioDeviceAttributes device = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_SCO, ""); + setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource); + if (!isBluetoothScoRequested()) { + Log.w(TAG, "startBluetoothScoForClient_Sync: rejected for pid: " + + pid + " mode owner pid: " + mModeOwnerPid); + postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } } } - @GuardedBy("mSetModeLock") - /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) { - synchronized (mDeviceStateLock) { - mBtHelper.stopBluetoothScoForClient(cb, eventSource); + /*package*/ void stopBluetoothScoForClient( + IBinder cb, int pid, @NonNull String eventSource) { + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid); + } + + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); + if (client == null || !client.requestsBluetoothSco()) { + return; + } + setCommunicationRouteForClient( + cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource); + } } } @@ -696,12 +762,8 @@ import java.util.concurrent.atomic.AtomicBoolean; hearingAidProfile); } - /*package*/ void postScoClientDied(Object obj) { - sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj); - } - - /*package*/ void postSpeakerphoneClientDied(Object obj) { - sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj); + /*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) { + sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client); } /*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy, @@ -821,15 +883,14 @@ import java.util.concurrent.atomic.AtomicBoolean; 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 + "mForcedUseForComm: " + AudioSystem.forceUseConfigToString(mForcedUseForComm)); - pw.println(prefix + "mForcedUseForCommExt: " - + AudioSystem.forceUseConfigToString(mForcedUseForCommExt)); pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid); - pw.println(prefix + "Speakerphone clients:"); - mSpeakerphoneClients.forEach((cl) -> { - pw.println(" " + prefix + "pid: " + cl.getPid() + " on: " - + cl.isOn() + " cb: " + cl.getBinder()); }); mBtHelper.dump(pw, prefix); } @@ -852,6 +913,11 @@ import java.util.concurrent.atomic.AtomicBoolean; .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 + ">)"); + } AudioSystem.setForceUse(useCase, config); } @@ -917,9 +983,12 @@ import java.util.concurrent.atomic.AtomicBoolean; public void handleMessage(Message msg) { switch (msg.what) { case MSG_RESTORE_DEVICES: - synchronized (mDeviceStateLock) { - mDeviceInventory.onRestoreDevices(); - mBtHelper.onAudioServerDiedRestoreA2dp(); + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + mDeviceInventory.onRestoreDevices(); + mBtHelper.onAudioServerDiedRestoreA2dp(); + onUpdateCommunicationRoute("MSG_RESTORE_DEVICES"); + } } break; case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: @@ -1009,25 +1078,24 @@ import java.util.concurrent.atomic.AtomicBoolean; if (mModeOwnerPid != msg.arg1) { mModeOwnerPid = msg.arg1; if (msg.arg2 != AudioSystem.MODE_RINGTONE) { - updateSpeakerphoneOn("setNewModeOwner"); - } - if (mModeOwnerPid != 0) { - mBtHelper.disconnectBluetoothSco(mModeOwnerPid); + onUpdateCommunicationRoute("setNewModeOwner"); } } } } break; - case MSG_L_SCOCLIENT_DIED: + case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - mBtHelper.scoClientDied(msg.obj); + onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj); } } break; - case MSG_L_SPEAKERPHONE_CLIENT_DIED: - synchronized (mDeviceStateLock) { - speakerphoneClientDied(msg.obj); + case MSG_L_UPDATE_COMMUNICATION_ROUTE: + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + onUpdateCommunicationRoute((String) msg.obj); + } } break; case MSG_TOGGLE_HDMI: @@ -1207,17 +1275,17 @@ import java.util.concurrent.atomic.AtomicBoolean; // process external command to (dis)connect a hearing aid device private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31; - // a ScoClient died in BtHelper - private static final int MSG_L_SCOCLIENT_DIED = 32; - private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 33; - private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 34; + 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_SPEAKERPHONE_CLIENT_DIED = 35; - private static final int MSG_CHECK_MUTE_MUSIC = 36; - private static final int MSG_REPORT_NEW_ROUTES_A2DP = 37; + 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 = 38; - private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 39; + 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_UPDATE_COMMUNICATION_ROUTE = 39; private static boolean isMessageHandledUnderWakelock(int msgId) { @@ -1372,14 +1440,20 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - private class SpeakerphoneClient implements IBinder.DeathRecipient { + // List of applications requesting a specific route for communication. + @GuardedBy("mDeviceStateLock") + private final @NonNull LinkedList mCommunicationRouteClients = + new LinkedList(); + + private class CommunicationRouteClient implements IBinder.DeathRecipient { private final IBinder mCb; private final int mPid; - private final boolean mOn; - SpeakerphoneClient(IBinder cb, int pid, boolean on) { + private AudioDeviceAttributes mDevice; + + CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) { mCb = cb; mPid = pid; - mOn = on; + mDevice = device; } public boolean registerDeathRecipient() { @@ -1388,7 +1462,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mCb.linkToDeath(this, 0); status = true; } catch (RemoteException e) { - Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death"); + Log.w(TAG, "CommunicationRouteClient could not link to " + mCb + " binder death"); } return status; } @@ -1397,13 +1471,13 @@ import java.util.concurrent.atomic.AtomicBoolean; try { mCb.unlinkToDeath(this, 0); } catch (NoSuchElementException e) { - Log.w(TAG, "SpeakerphoneClient could not not unregistered to binder"); + Log.w(TAG, "CommunicationRouteClient could not not unregistered to binder"); } } @Override public void binderDied() { - postSpeakerphoneClientDied(this); + postCommunicationRouteClientDied(this); } IBinder getBinder() { @@ -1414,29 +1488,99 @@ import java.util.concurrent.atomic.AtomicBoolean; return mPid; } - boolean isOn() { - return mOn; + AudioDeviceAttributes getDevice() { + return mDevice; + } + + boolean requestsBluetoothSco() { + return mDevice != null + && mDevice.getType() + == AudioDeviceInfo.TYPE_BLUETOOTH_SCO; + } + + boolean requestsSpeakerphone() { + return mDevice != null + && mDevice.getType() + == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; } } + // @GuardedBy("mSetModeLock") @GuardedBy("mDeviceStateLock") - private void speakerphoneClientDied(Object obj) { - if (obj == null) { + private void onCommunicationRouteClientDied(CommunicationRouteClient client) { + if (client == null) { return; } Log.w(TAG, "Speaker client died"); - if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) { - updateSpeakerphoneOn("speakerphoneClientDied"); + if (removeCommunicationRouteClient(client.getBinder(), false) + != null) { + onUpdateCommunicationRoute("onCommunicationRouteClientDied"); } } - private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) { - for (SpeakerphoneClient cl : mSpeakerphoneClients) { + /** + * Determines which forced usage for communication 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") + private int getForcedUseForComm() { + boolean btSCoOn = mBluetoothScoOn && mBtHelper.isBluetoothScoOn(); + + if (btSCoOn) { + return AudioSystem.FORCE_BT_SCO; + } + if (isSpeakerphoneRequested()) { + return AudioSystem.FORCE_SPEAKER; + } + return AudioSystem.FORCE_NONE; + } + + /** + * Configures audio policy manager and audio HAL according to active communication route. + * Always called from message Handler. + */ + // @GuardedBy("mSetModeLock") + @GuardedBy("mDeviceStateLock") + private void onUpdateCommunicationRoute(String eventSource) { + mForcedUseForComm = getForcedUseForComm(); + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "onUpdateCommunicationRoute, mForcedUseForComm: " + mForcedUseForComm + + " eventSource: " + eventSource); + } + + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + AudioSystem.setParameters("BT_SCO=on"); + setForceUse_Async( + AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_BT_SCO, eventSource); + setForceUse_Async( + AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource); + } else { + AudioSystem.setParameters("BT_SCO=off"); + setForceUse_Async( + AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { + setForceUse_Async( + AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_SPEAKER, eventSource); + } else { + setForceUse_Async( + AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_NONE, eventSource); + } + } + mAudioService.postUpdateRingerModeServiceInt(); + } + + private CommunicationRouteClient removeCommunicationRouteClient( + IBinder cb, boolean unregister) { + for (CommunicationRouteClient cl : mCommunicationRouteClients) { if (cl.getBinder() == cb) { if (unregister) { cl.unregisterDeathRecipient(); } - mSpeakerphoneClients.remove(cl); + mCommunicationRouteClients.remove(cl); return cl; } } @@ -1444,30 +1588,25 @@ import java.util.concurrent.atomic.AtomicBoolean; } @GuardedBy("mDeviceStateLock") - private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) { + private CommunicationRouteClient addCommunicationRouteClient( + IBinder cb, int pid, AudioDeviceAttributes device) { // always insert new request at first position - removeSpeakerphoneClient(cb, true); - SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on); + removeCommunicationRouteClient(cb, true); + CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device); if (client.registerDeathRecipient()) { - mSpeakerphoneClients.add(0, client); - return true; + mCommunicationRouteClients.add(0, client); + return client; } - return false; + return null; } @GuardedBy("mDeviceStateLock") - private SpeakerphoneClient getSpeakerphoneClientForPid(int pid) { - for (SpeakerphoneClient cl : mSpeakerphoneClients) { + private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) { + for (CommunicationRouteClient cl : mCommunicationRouteClients) { if (cl.getPid() == pid) { return cl; } } return null; } - - // List of clients requesting speakerPhone ON - @GuardedBy("mDeviceStateLock") - private final @NonNull ArrayList mSpeakerphoneClients = - new ArrayList(); - } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index a75d65040a6b9..569640ae459d8 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -207,6 +207,9 @@ public class AudioService extends IAudioService.Stub /** debug calls to devices APIs */ protected static final boolean DEBUG_DEVICES = false; + /** Debug communication route */ + protected static final boolean DEBUG_COMM_RTE = false; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -3689,7 +3692,7 @@ public class AudioService extends IAudioService.Stub final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE || ringerMode == AudioManager.RINGER_MODE_SILENT; final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE - && isBluetoothScoOn(); + && mDeviceBroker.isBluetoothScoOn(); // Ask audio policy engine to force use Bluetooth SCO channel if needed final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid() + "/" + Binder.getCallingPid(); @@ -4274,10 +4277,10 @@ public class AudioService extends IAudioService.Stub // for logging only final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); + final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); - final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource); new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR + "setSpeakerphoneOn") .setUid(uid) @@ -4285,17 +4288,9 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.STATE, on ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF) .record(); - - if (stateChanged) { - final long ident = Binder.clearCallingIdentity(); - try { - mContext.sendBroadcastAsUser( - new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL); - } finally { - Binder.restoreCallingIdentity(ident); - } - } + final long ident = Binder.clearCallingIdentity(); + mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource); + Binder.restoreCallingIdentity(ident); } /** @see AudioManager#isSpeakerphoneOn() */ @@ -4303,6 +4298,11 @@ public class AudioService extends IAudioService.Stub return mDeviceBroker.isSpeakerphoneOn(); } + + /** BT SCO audio state seen by apps using the deprecated API setBluetoothScoOn(). + * @see isBluetoothScoOn() */ + private boolean mBtScoOnByApp; + /** @see AudioManager#setBluetoothScoOn(boolean) */ public void setBluetoothScoOn(boolean on) { if (!checkAudioSettingsPermission("setBluetoothScoOn()")) { @@ -4311,7 +4311,7 @@ public class AudioService extends IAudioService.Stub // Only enable calls from system components if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) { - mDeviceBroker.setBluetoothScoOnByApp(on); + mBtScoOnByApp = on; return; } @@ -4337,7 +4337,7 @@ public class AudioService extends IAudioService.Stub * Note that it doesn't report internal state, but state seen by apps (which may have * called setBluetoothScoOn() */ public boolean isBluetoothScoOn() { - return mDeviceBroker.isBluetoothScoOnForApp(); + return mBtScoOnByApp || mDeviceBroker.isBluetoothScoOn(); } // TODO investigate internal users due to deprecation of SDK API @@ -4384,7 +4384,7 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.SCO_AUDIO_MODE, BtHelper.scoAudioModeToString(scoAudioMode)) .record(); - startBluetoothScoInt(cb, scoAudioMode, eventSource); + startBluetoothScoInt(cb, pid, scoAudioMode, eventSource); } @@ -4403,10 +4403,10 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.SCO_AUDIO_MODE, BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL)) .record(); - startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); + startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } - void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) { + void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) { MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH) .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt") .set(MediaMetrics.Property.SCO_AUDIO_MODE, @@ -4417,9 +4417,9 @@ public class AudioService extends IAudioService.Stub mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record(); return; } - synchronized (mDeviceBroker.mSetModeLock) { - mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource); - } + final long ident = Binder.clearCallingIdentity(); + mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource); + Binder.restoreCallingIdentity(ident); mmi.record(); } @@ -4434,9 +4434,9 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("stopBluetoothSco()") .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); - synchronized (mDeviceBroker.mSetModeLock) { - mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource); - } + final long ident = Binder.clearCallingIdentity(); + mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource); + Binder.restoreCallingIdentity(ident); new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH) .setUid(uid) .setPid(pid) @@ -7670,6 +7670,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); + pw.print(" mBtScoOnByApp="); pw.println(mBtScoOnByApp); pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume); pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume); pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices)); diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 7616557ac80f3..42820bba164f2 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -30,8 +30,6 @@ import android.content.Intent; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -39,9 +37,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; -import java.util.NoSuchElementException; import java.util.Objects; /** @@ -58,10 +54,6 @@ public class BtHelper { mDeviceBroker = broker; } - // List of clients having issued a SCO start request - @GuardedBy("BtHelper.this") - private final @NonNull ArrayList mScoClients = new ArrayList(); - // BluetoothHeadset API to control SCO connection private @Nullable BluetoothHeadset mBluetoothHeadset; @@ -301,6 +293,8 @@ public class BtHelper { @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void receiveBtEvent(Intent intent) { final String action = intent.getAction(); + + Log.i(TAG, "receiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState); if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); setBtScoActiveDevice(btDevice); @@ -308,20 +302,16 @@ public class BtHelper { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - // broadcast intent if the connection was initated by AudioService - if (!mScoClients.isEmpty() - && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL - || mScoAudioState == SCO_STATE_ACTIVATE_REQ - || mScoAudioState == SCO_STATE_DEACTIVATE_REQ - || mScoAudioState == SCO_STATE_DEACTIVATING)) { - broadcast = true; - } + Log.i(TAG, "receiveBtEvent ACTION_AUDIO_STATE_CHANGED: " + btState); switch (btState) { case BluetoothHeadset.STATE_AUDIO_CONNECTED: scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } else if (mDeviceBroker.isBluetoothScoRequested()) { + // broadcast intent if the connection was initated by AudioService + broadcast = true; } mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); break; @@ -333,21 +323,21 @@ public class BtHelper { // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. // 2) If audio was connected then disconnected via Bluetooth APIs and // we still have pending activation requests by apps: this is indicated by - // state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty. + // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL - && !mScoClients.isEmpty())) { + && mDeviceBroker.isBluetoothScoRequested())) { if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && connectBluetoothScoAudioHelper(mBluetoothHeadset, mBluetoothHeadsetDevice, mScoAudioMode)) { mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcast = false; + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; + broadcast = true; break; } } - // Tear down SCO if disconnected from external - if (mScoAudioState == SCO_STATE_DEACTIVATING) { - clearAllScoClients(0, false); + if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { + broadcast = true; } mScoAudioState = SCO_STATE_INACTIVE; break; @@ -356,11 +346,8 @@ public class BtHelper { && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; } - broadcast = false; break; default: - // do not broadcast CONNECTING or invalid state - broadcast = false; break; } if (broadcast) { @@ -386,81 +373,19 @@ public class BtHelper { == BluetoothHeadset.STATE_AUDIO_CONNECTED; } - /** - * Disconnect all SCO connections started by {@link AudioManager} except those started by - * {@param exceptPid} - * - * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept - */ // @GuardedBy("AudioDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) { - checkScoAudioState(); - if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { - return; - } - clearAllScoClients(exceptPid, true); - } - - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode, + /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode, @NonNull String eventSource) { - ScoClient client = getScoClient(cb, true); - // The calling identity must be cleared before calling ScoClient.incCount(). - // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - // The caller identity must be cleared after getScoClient() because it is needed if a new - // client is created. - final long ident = Binder.clearCallingIdentity(); - try { - AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); - client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); - } catch (NullPointerException e) { - Log.e(TAG, "Null ScoClient", e); - } - Binder.restoreCallingIdentity(ident); - } - - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb, - @NonNull String eventSource) { - ScoClient client = getScoClient(cb, false); - // The calling identity must be cleared before calling ScoClient.decCount(). - // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - final long ident = Binder.clearCallingIdentity(); - if (client != null) { - stopAndRemoveClient(client, eventSource); - } - Binder.restoreCallingIdentity(ident); - } - - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void stopBluetoothScoForPid(int pid) { - ScoClient client = getScoClientForPid(pid); - if (client == null) { - return; - } - final String eventSource = new StringBuilder("stopBluetoothScoForPid(") - .append(pid).append(")").toString(); - stopAndRemoveClient(client, eventSource); - } - - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - // @GuardedBy("BtHelper.this") - private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); - client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, - SCO_MODE_VIRTUAL_CALL); - // If a disconnection is pending, the client will be removed when clearAllScoClients() - // is called form receiveBtEvent() - if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ - && mScoAudioState != SCO_STATE_DEACTIVATING) { - client.remove(false /*stop */, true /*unregister*/); - } + return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); + } + + // @GuardedBy("AudioDeviceBroker.mSetModeLock") + @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL); } /*package*/ synchronized void setHearingAidVolume(int index, int streamType) { @@ -507,7 +432,6 @@ public class BtHelper { // @GuardedBy("AudioDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void resetBluetoothSco() { - clearAllScoClients(0, false); mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); AudioSystem.setParameters("A2dpSuspended=false"); @@ -733,195 +657,122 @@ public class BtHelper { }; //---------------------------------------------------------------------- + // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void scoClientDied(Object obj) { - final ScoClient client = (ScoClient) obj; - client.remove(true /*stop*/, false /*unregister*/); - Log.w(TAG, "SCO client died"); - } - - private class ScoClient implements IBinder.DeathRecipient { - private IBinder mCb; // To be notified of client's death - private int mCreatorPid; - - ScoClient(IBinder cb) { - mCb = cb; - mCreatorPid = Binder.getCallingPid(); - } - - public void registerDeathRecipient() { - try { - mCb.linkToDeath(this, 0); - } catch (RemoteException e) { - Log.w(TAG, "ScoClient could not link to " + mCb + " binder death"); - } - } - - public void unregisterDeathRecipient() { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "ScoClient could not not unregistered to binder"); - } - } - - @Override - public void binderDied() { - // process this from DeviceBroker's message queue to take the right locks since - // this event can impact SCO mode and requires querying audio mode stack - mDeviceBroker.postScoClientDied(this); - } - - IBinder getBinder() { - return mCb; - } - - int getPid() { - return mCreatorPid; - } - - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") - @GuardedBy("BtHelper.this") - private boolean requestScoState(int state, int scoAudioMode) { - checkScoAudioState(); - if (mScoClients.size() != 1) { - Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode - + ", num SCO clients=" + mScoClients.size()); - return true; - } - if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { - // Make sure that the state transitions to CONNECTING even if we cannot initiate - // the connection. - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); - // Accept SCO audio activation only in NORMAL audio mode or if the mode is - // currently controlled by the same client process. - final int modeOwnerPid = mDeviceBroker.getModeOwnerPid(); - if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { - Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " - + modeOwnerPid + " != creatorPid " + mCreatorPid); + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") + @GuardedBy("BtHelper.this") + private boolean requestScoState(int state, int scoAudioMode) { + checkScoAudioState(); + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + // Make sure that the state transitions to CONNECTING even if we cannot initiate + // the connection. + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); + switch (mScoAudioState) { + case SCO_STATE_INACTIVE: + mScoAudioMode = scoAudioMode; + if (scoAudioMode == SCO_MODE_UNDEFINED) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + if (mBluetoothHeadsetDevice != null) { + mScoAudioMode = Settings.Global.getInt( + mDeviceBroker.getContentResolver(), + "bluetooth_sco_channel_" + + mBluetoothHeadsetDevice.getAddress(), + SCO_MODE_VIRTUAL_CALL); + if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + } + } + } + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " connection, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return false; + } + break; + } + if (mBluetoothHeadsetDevice == null) { + Log.w(TAG, "requestScoState: no active device while connecting," + + " mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return false; + } + if (connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } else { + Log.w(TAG, "requestScoState: connect to " + + getAnonymizedAddress(mBluetoothHeadsetDevice) + + " failed, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return false; + } + break; + case SCO_STATE_DEACTIVATING: + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + break; + case SCO_STATE_DEACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + break; + case SCO_STATE_ACTIVE_INTERNAL: + Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return"); + break; + default: + Log.w(TAG, "requestScoState: failed to connect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); return false; - } - switch (mScoAudioState) { - case SCO_STATE_INACTIVE: - mScoAudioMode = scoAudioMode; - if (scoAudioMode == SCO_MODE_UNDEFINED) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - if (mBluetoothHeadsetDevice != null) { - mScoAudioMode = Settings.Global.getInt( - mDeviceBroker.getContentResolver(), - "bluetooth_sco_channel_" - + mBluetoothHeadsetDevice.getAddress(), - SCO_MODE_VIRTUAL_CALL); - if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - } - } - } - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " connection, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return false; - } - break; - } - if (mBluetoothHeadsetDevice == null) { - Log.w(TAG, "requestScoState: no active device while connecting," - + " mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return false; - } - if (connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVE_INTERNAL: + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_REQ; } else { - Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice - + " failed, mScoAudioMode=" + mScoAudioMode); + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " disconnection, mScoAudioMode=" + mScoAudioMode); + mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState( AudioManager.SCO_AUDIO_STATE_DISCONNECTED); return false; } break; - case SCO_STATE_DEACTIVATING: - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - break; - case SCO_STATE_DEACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); - break; - case SCO_STATE_ACTIVE_INTERNAL: - Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return"); - break; - default: - Log.w(TAG, "requestScoState: failed to connect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return false; - } - } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVE_INTERNAL: - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_DEACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " disconnection, mScoAudioMode=" + mScoAudioMode); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return false; - } - break; - } - if (mBluetoothHeadsetDevice == null) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } else { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_ACTIVATE_REQ: + } + if (mBluetoothHeadsetDevice == null) { mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); break; - default: - Log.w(TAG, "requestScoState: failed to disconnect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return false; - } + } + if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } else { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to disconnect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return false; } - return true; - } - - @GuardedBy("BtHelper.this") - void remove(boolean stop, boolean unregister) { - if (unregister) { - unregisterDeathRecipient(); - } - if (stop) { - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, - SCO_MODE_VIRTUAL_CALL); - } - mScoClients.remove(this); } + return true; } //----------------------------------------------------- @@ -974,49 +825,6 @@ public class BtHelper { } } - - @GuardedBy("BtHelper.this") - private ScoClient getScoClient(IBinder cb, boolean create) { - for (ScoClient existingClient : mScoClients) { - if (existingClient.getBinder() == cb) { - return existingClient; - } - } - if (create) { - ScoClient newClient = new ScoClient(cb); - newClient.registerDeathRecipient(); - mScoClients.add(newClient); - return newClient; - } - return null; - } - - @GuardedBy("BtHelper.this") - private ScoClient getScoClientForPid(int pid) { - for (ScoClient cl : mScoClients) { - if (cl.getPid() == pid) { - return cl; - } - } - return null; - } - - // @GuardedBy("AudioDeviceBroker.mSetModeLock") - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") - @GuardedBy("BtHelper.this") - private void clearAllScoClients(int exceptPid, boolean stopSco) { - final ArrayList clients = new ArrayList(); - for (ScoClient cl : mScoClients) { - if (cl.getPid() != exceptPid) { - clients.add(cl); - } - } - for (ScoClient cl : clients) { - cl.remove(stopSco, true /*unregister*/); - } - - } - private boolean getBluetoothHeadset() { boolean result = false; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -1062,10 +870,6 @@ public class BtHelper { pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice); pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState)); pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode)); - pw.println(prefix + "Sco clients:"); - mScoClients.forEach((cl) -> { - pw.println(" " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); }); - pw.println("\n" + prefix + "mHearingAid: " + mHearingAid); pw.println(prefix + "mA2dp: " + mA2dp); pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported); From f39726f2620b152b03e2697fadc5283cd3e002d7 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Fri, 9 Oct 2020 09:52:49 -0700 Subject: [PATCH 2/3] AudioManager: Add communication device management APIs Add new APIs to manage the audio device used for communication use cases (Cellular calls, VoIP and Video calls). These APIs are meant to replace specific APIs like setSpeakerPhoneOn() and provide a single generic way of configuring the audio device selected for calls. They will be used for newly added device types (e.g. BLE audio) instead of adding new specific APIs. They will also offer more options like for instance allowing to select the earpiece (handset) device when a wired headset is connected. The new APIs are: - boolean setDeviceForCommunication(AudioDeviceInfo) - void clearDeviceForCommunication() - AudioDeviceInfo getDeviceForCommunication() A listener is also added for applications to monitor current communication device selection: - OnCommunicationDeviceChangedListener As well as listener registration and unregistration APIs: - addOnCommunicationDeviceChangedListener() - removeOnCommunicationDeviceChangedListener() Bug: 161358428 Test: make && atest AudioCommunicationDeviceTest Change-Id: I8028d842e4a8ca1abe0f87d03e3c5d57c46b9362 Merged-In: I8028d842e4a8ca1abe0f87d03e3c5d57c46b9362 --- core/api/current.txt | 9 + core/api/test-current.txt | 1 + media/java/android/media/AudioManager.java | 314 ++++++++++++++++++ media/java/android/media/IAudioService.aidl | 10 + .../media/ICommunicationDeviceDispatcher.aidl | 28 ++ .../server/audio/AudioDeviceBroker.java | 94 +++++- .../android/server/audio/AudioService.java | 110 ++++++ .../com/android/server/audio/BtHelper.java | 2 +- 8 files changed, 562 insertions(+), 6 deletions(-) create mode 100644 media/java/android/media/ICommunicationDeviceDispatcher.aidl diff --git a/core/api/current.txt b/core/api/current.txt index 74d313c636b75..65d3e4a36b9de 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -19373,14 +19373,17 @@ package android.media { public class AudioManager { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener); method public int abandonAudioFocusRequest(@NonNull android.media.AudioFocusRequest); + method public void addOnCommunicationDeviceChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener); method public void adjustStreamVolume(int, int, int); method public void adjustSuggestedStreamVolume(int, int, int); method public void adjustVolume(int, int); + method public void clearDeviceForCommunication(); method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public int generateAudioSessionId(); method @NonNull public java.util.List getActivePlaybackConfigurations(); method @NonNull public java.util.List getActiveRecordingConfigurations(); method public int getAllowedCapturePolicy(); + method @Nullable public android.media.AudioDeviceInfo getDeviceForCommunication(); method public android.media.AudioDeviceInfo[] getDevices(int); method public java.util.List getMicrophones() throws java.io.IOException; method public int getMode(); @@ -19415,11 +19418,13 @@ package android.media { method @Deprecated public void registerMediaButtonEventReceiver(android.app.PendingIntent); method @Deprecated public void registerRemoteControlClient(android.media.RemoteControlClient); method @Deprecated public boolean registerRemoteController(android.media.RemoteController); + method public void removeOnCommunicationDeviceChangedListener(@NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener); method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int); method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest); method public void setAllowedCapturePolicy(int); method @Deprecated public void setBluetoothA2dpOn(boolean); method public void setBluetoothScoOn(boolean); + method public boolean setDeviceForCommunication(@NonNull android.media.AudioDeviceInfo); method public void setMicrophoneMute(boolean); method public void setMode(int); method public void setParameters(String); @@ -19554,6 +19559,10 @@ package android.media { method public void onAudioFocusChange(int); } + public static interface AudioManager.OnCommunicationDeviceChangedListener { + method public void onCommunicationDeviceChanged(@Nullable android.media.AudioDeviceInfo); + } + public final class AudioMetadata { method @NonNull public static android.media.AudioMetadataMap createMap(); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b6bd687a3a88c..9cf9ce45602b8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -850,6 +850,7 @@ package android.media { } public class AudioManager { + method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int); method public boolean hasRegisteredDynamicPolicy(); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ed9e5175fb781..7dff0c2b93806 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6123,6 +6123,29 @@ public class AudioManager { return infoListFromPortList(ports, flags); } + /** + * Returns an {@link AudioDeviceInfo} corresponding to the specified {@link AudioPort} ID. + * @param portId The audio port ID to look up for. + * @param flags A set of bitflags specifying the criteria to test. + * @see #GET_DEVICES_OUTPUTS + * @see #GET_DEVICES_INPUTS + * @see #GET_DEVICES_ALL + * @return An AudioDeviceInfo or null if no device with matching port ID is found. + * @hide + */ + public static AudioDeviceInfo getDeviceForPortId(int portId, int flags) { + if (portId == 0) { + return null; + } + AudioDeviceInfo[] devices = getDevicesStatic(flags); + for (AudioDeviceInfo device : devices) { + if (device.getId() == portId) { + return device; + } + } + return null; + } + /** * Registers an {@link AudioDeviceCallback} object to receive notifications of changes * to the set of connected audio devices. @@ -6666,6 +6689,297 @@ public class AudioManager { } } + /** + * Selects the audio device that should be used for communication use cases, for instance voice + * or video calls. This method can be used by voice or video chat applications to select a + * different audio device than the one selected by default by the platform. + *

The device selection is expressed as an {@link AudioDeviceInfo}, of role sink + * ({@link AudioDeviceInfo#isSink()} is true) and of one of the following types: + *

    + *
  • {@link AudioDeviceInfo#TYPE_BUILTIN_EARPIECE} + *
  • {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER} + *
  • {@link AudioDeviceInfo#TYPE_WIRED_HEADSET} + *
  • {@link AudioDeviceInfo#TYPE_BLUETOOTH_SCO} + *
  • {@link AudioDeviceInfo#TYPE_USB_HEADSET} + *
  • {@link AudioDeviceInfo#TYPE_BLE_HEADSET} + *
+ * The selection is active as long as the requesting application lives, until + * {@link #clearDeviceForCommunication} is called or until the device is disconnected. + * It is therefore important for applications to clear the request when a call ends or the + * application is paused. + *

In case of simultaneous requests by multiple applications the priority is given to the + * application currently controlling the audio mode (see {@link #setMode(int)}). This is the + * latest application having selected mode {@link #MODE_IN_COMMUNICATION} or mode + * {@link #MODE_IN_CALL}. Note that MODE_IN_CALL can only be selected by the main + * telephony application with permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE}. + *

If the requested devices is not currently available, the request will be rejected and + * the method will return false. + *

This API replaces the following deprecated APIs: + *

    + *
  • {@link #startBluetoothSco()} + *
  • {@link #stopBluetoothSco()} + *
  • {@link #setSpeakerphoneOn(boolean)} + *
+ *

Example

+ *

The example below shows how to enable and disable speakerphone mode. + *

+     * // Get an AudioManager instance
+     * AudioManager audioManager = Context.getSystemService(AudioManager.class);
+     * try {
+     *     AudioDeviceInfo speakerDevice = null;
+     *     AudioDeviceInfo[] devices = audioManager.getDevices(GET_DEVICES_OUTPUTS);
+     *     for (AudioDeviceInfo device : devices) {
+     *         if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+     *             speakerDevice = device;
+     *             break;
+     *         }
+     *     }
+     *     if (speakerDevice != null) {
+     *         // Turn speakerphone ON.
+     *         boolean result = audioManager.setDeviceForCommunication(speakerDevice);
+     *         if (!result) {
+     *             // Handle error.
+     *         }
+     *         // Turn speakerphone OFF.
+     *         audioManager.clearDeviceForCommunication();
+     *     }
+     * } catch (IllegalArgumentException e) {
+     *     // Handle exception.
+     * }
+     * 
+ * @param device the requested audio device. + * @return true if the request was accepted, false otherwise. + * @throws IllegalArgumentException If an invalid device is specified. + */ + public boolean setDeviceForCommunication(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device); + try { + if (device.getId() == 0) { + throw new IllegalArgumentException("In valid device: " + device); + } + return getService().setDeviceForCommunication(mICallBack, device.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Cancels previous communication device selection made with + * {@link #setDeviceForCommunication(AudioDeviceInfo)}. + */ + public void clearDeviceForCommunication() { + try { + getService().setDeviceForCommunication(mICallBack, 0); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns currently selected audio device for communication. + *

This API replaces the following deprecated APIs: + *

    + *
  • {@link #isBluetoothScoOn()} + *
  • {@link #isSpeakerphoneOn()} + *
+ * @return an {@link AudioDeviceInfo} indicating which audio device is + * currently selected or communication use cases or null if default selection + * is used. + */ + @Nullable + public AudioDeviceInfo getDeviceForCommunication() { + try { + return getDeviceForPortId( + getService().getDeviceForCommunication(), GET_DEVICES_OUTPUTS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type provided. + * The type must be a valid output type defined in AudioDeviceInfo class, + * for instance {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. + * The method will return null if no device of the provided type is connected. + * If more than one device of the provided type is connected, an object corresponding to the + * first device encountered in the enumeration list will be returned. + * @param deviceType The device device for which an AudioDeviceInfo + * object is queried. + * @return An AudioDeviceInfo object or null if no device with the requested type is connected. + * @throws IllegalArgumentException If an invalid device type is specified. + */ + @TestApi + @Nullable + public static AudioDeviceInfo getDeviceInfoFromType( + @AudioDeviceInfo.AudioDeviceTypeOut int deviceType) { + AudioDeviceInfo[] devices = getDevicesStatic(GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : devices) { + if (device.getType() == deviceType) { + return device; + } + } + return null; + } + + /** + * Listener registered by client to be notified upon communication audio device change. + * See {@link #setDeviceForCommunication(AudioDeviceInfo)}. + */ + public interface OnCommunicationDeviceChangedListener { + /** + * Callback method called upon communication audio device change. + * @param device the audio device selected for communication use cases + */ + void onCommunicationDeviceChanged(@Nullable AudioDeviceInfo device); + } + + /** + * Adds a listener for being notified of changes to the communication audio device. + * See {@link #setDeviceForCommunication(AudioDeviceInfo)}. + * @param executor + * @param listener + */ + public void addOnCommunicationDeviceChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnCommunicationDeviceChangedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mCommDevListenerLock) { + if (hasCommDevListener(listener)) { + throw new IllegalArgumentException( + "attempt to call addOnCommunicationDeviceChangedListener() " + + "on a previously registered listener"); + } + // lazy initialization of the list of strategy-preferred device listener + if (mCommDevListeners == null) { + mCommDevListeners = new ArrayList<>(); + } + final int oldCbCount = mCommDevListeners.size(); + mCommDevListeners.add(new CommDevListenerInfo(listener, executor)); + if (oldCbCount == 0 && mCommDevListeners.size() > 0) { + // register binder for callbacks + if (mCommDevDispatcherStub == null) { + mCommDevDispatcherStub = new CommunicationDeviceDispatcherStub(); + } + try { + getService().registerCommunicationDeviceDispatcher(mCommDevDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Removes a previously added listener of changes to the communication audio device. + * See {@link #setDeviceForCommunication(AudioDeviceInfo)}. + * @param listener + */ + public void removeOnCommunicationDeviceChangedListener( + @NonNull OnCommunicationDeviceChangedListener listener) { + Objects.requireNonNull(listener); + synchronized (mCommDevListenerLock) { + if (!removeCommDevListener(listener)) { + throw new IllegalArgumentException( + "attempt to call removeOnCommunicationDeviceChangedListener() " + + "on an unregistered listener"); + } + if (mCommDevListeners.size() == 0) { + // unregister binder for callbacks + try { + getService().unregisterCommunicationDeviceDispatcher( + mCommDevDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + mCommDevDispatcherStub = null; + mCommDevListeners = null; + } + } + } + } + + private final Object mCommDevListenerLock = new Object(); + /** + * List of listeners for preferred device for strategy and their associated Executor. + * List is lazy-initialized on first registration + */ + @GuardedBy("mCommDevListenerLock") + private @Nullable ArrayList mCommDevListeners; + + private static class CommDevListenerInfo { + final @NonNull OnCommunicationDeviceChangedListener mListener; + final @NonNull Executor mExecutor; + + CommDevListenerInfo(OnCommunicationDeviceChangedListener listener, Executor exe) { + mListener = listener; + mExecutor = exe; + } + } + + @GuardedBy("mCommDevListenerLock") + private CommunicationDeviceDispatcherStub mCommDevDispatcherStub; + + private final class CommunicationDeviceDispatcherStub + extends ICommunicationDeviceDispatcher.Stub { + + @Override + public void dispatchCommunicationDeviceChanged(int portId) { + // make a shallow copy of listeners so callback is not executed under lock + final ArrayList commDevListeners; + synchronized (mCommDevListenerLock) { + if (mCommDevListeners == null || mCommDevListeners.size() == 0) { + return; + } + commDevListeners = (ArrayList) mCommDevListeners.clone(); + } + AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS); + final long ident = Binder.clearCallingIdentity(); + try { + for (CommDevListenerInfo info : commDevListeners) { + info.mExecutor.execute(() -> + info.mListener.onCommunicationDeviceChanged(device)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @GuardedBy("mCommDevListenerLock") + private @Nullable CommDevListenerInfo getCommDevListenerInfo( + OnCommunicationDeviceChangedListener listener) { + if (mCommDevListeners == null) { + return null; + } + for (CommDevListenerInfo info : mCommDevListeners) { + if (info.mListener == listener) { + return info; + } + } + return null; + } + + @GuardedBy("mCommDevListenerLock") + private boolean hasCommDevListener(OnCommunicationDeviceChangedListener listener) { + return getCommDevListenerInfo(listener) != null; + } + + @GuardedBy("mCommDevListenerLock") + /** + * @return true if the listener was removed from the list + */ + private boolean removeCommDevListener(OnCommunicationDeviceChangedListener listener) { + final CommDevListenerInfo infoToRemove = getCommDevListenerInfo(listener); + if (infoToRemove != null) { + mCommDevListeners.remove(infoToRemove); + return true; + } + return false; + } + //--------------------------------------------------------- // Inner classes //-------------------- diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index d9b44cdd20e75..ebaa3162d0e46 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -27,6 +27,7 @@ import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; import android.media.ICapturePresetDevicesRoleDispatcher; +import android.media.ICommunicationDeviceDispatcher; import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; @@ -320,4 +321,13 @@ interface IAudioService { oneway void unregisterCapturePresetDevicesRoleDispatcher( ICapturePresetDevicesRoleDispatcher dispatcher); + + boolean setDeviceForCommunication(IBinder cb, int portId); + + int getDeviceForCommunication(); + + void registerCommunicationDeviceDispatcher(ICommunicationDeviceDispatcher dispatcher); + + oneway void unregisterCommunicationDeviceDispatcher( + ICommunicationDeviceDispatcher dispatcher); } diff --git a/media/java/android/media/ICommunicationDeviceDispatcher.aidl b/media/java/android/media/ICommunicationDeviceDispatcher.aidl new file mode 100644 index 0000000000000..429f934a77dc1 --- /dev/null +++ b/media/java/android/media/ICommunicationDeviceDispatcher.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 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 android.media; + +/** + * AIDL for AudioService to signal audio communication device updates. + * + * {@hide} + */ +oneway interface ICommunicationDeviceDispatcher { + + void dispatchCommunicationDeviceChanged(int portId); + +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 3b407f14297ea..f04c5eb030dba 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -31,6 +31,7 @@ import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioRoutesObserver; import android.media.ICapturePresetDevicesRoleDispatcher; +import android.media.ICommunicationDeviceDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.MediaMetrics; import android.os.Binder; @@ -39,6 +40,7 @@ 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; @@ -233,6 +235,38 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + /** + * 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 + */ + /*package*/ boolean setDeviceForCommunication( + IBinder cb, int pid, AudioDeviceInfo device, String eventSource) { + + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "setDeviceForCommunication, device: " + device + ", pid: " + pid); + } + + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + AudioDeviceAttributes deviceAttr = null; + if (device != null) { + deviceAttr = new AudioDeviceAttributes(device); + } else { + CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); + if (client == null) { + return false; + } + } + setCommunicationRouteForClient( + cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource); + } + } + return true; + } + @GuardedBy("mDeviceStateLock") /*package*/ void setCommunicationRouteForClient( IBinder cb, int pid, AudioDeviceAttributes device, @@ -278,6 +312,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } else { removeCommunicationRouteClient(cb, true); } + postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } } else if (!isBtScoRequested && wasBtScoRequested) { mBtHelper.stopBluetoothSco(eventSource); @@ -324,6 +359,20 @@ import java.util.concurrent.atomic.AtomicBoolean; return device; } + /** + * Returns the device currently requested for communication use case. + * @return AudioDeviceInfo the requested device for communication. + */ + AudioDeviceInfo getDeviceForCommunication() { + synchronized (mDeviceStateLock) { + AudioDeviceAttributes device = requestedCommunicationDevice(); + if (device == null) { + return null; + } + return AudioManager.getDeviceInfoFromType(device.getType()); + } + } + /** * Helper method on top of requestedCommunicationDevice() indicating if * speakerphone ON is currently requested or not. @@ -566,11 +615,6 @@ import java.util.concurrent.atomic.AtomicBoolean; AudioDeviceAttributes device = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_SCO, ""); setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource); - if (!isBluetoothScoRequested()) { - Log.w(TAG, "startBluetoothScoForClient_Sync: rejected for pid: " - + pid + " mode owner pid: " + mModeOwnerPid); - postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } } } } @@ -632,6 +676,45 @@ import java.util.concurrent.atomic.AtomicBoolean; 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 mCommDevDispatchers = + new RemoteCallbackList(); + + // 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 = getDeviceForCommunication(); + int portId = (getDeviceForCommunication() == null) ? 0 : device.getId(); + 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 @@ -1571,6 +1654,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } } mAudioService.postUpdateRingerModeServiceInt(); + dispatchCommunicationDevice(); } private CommunicationRouteClient removeCommunicationRouteClient( diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 569640ae459d8..88b12253d723a 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -85,6 +85,7 @@ import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; import android.media.IAudioService; import android.media.ICapturePresetDevicesRoleDispatcher; +import android.media.ICommunicationDeviceDispatcher; import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; @@ -4268,6 +4269,115 @@ public class AudioService extends IAudioService.Stub restoreDeviceVolumeBehavior(); } + 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 + }; + + private boolean isValidCommunicationDevice(AudioDeviceInfo device) { + for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { + if (device.getType() == type) { + return true; + } + } + return false; + } + + /** @see AudioManager#setDeviceForCommunication(int) */ + public boolean setDeviceForCommunication(IBinder cb, int portId) { + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + + AudioDeviceInfo device = null; + if (portId != 0) { + device = AudioManager.getDeviceForPortId(portId, AudioManager.GET_DEVICES_OUTPUTS); + if (device == null) { + throw new IllegalArgumentException("invalid portID " + portId); + } + if (!isValidCommunicationDevice(device)) { + throw new IllegalArgumentException("invalid device type " + device.getType()); + } + } + final String eventSource = new StringBuilder("setDeviceForCommunication(") + .append(") from u/pid:").append(uid).append("/") + .append(pid).toString(); + + int deviceType = AudioSystem.DEVICE_OUT_DEFAULT; + String deviceAddress = null; + if (device != null) { + deviceType = device.getPort().type(); + deviceAddress = device.getAddress(); + } else { + AudioDeviceInfo curDevice = mDeviceBroker.getDeviceForCommunication(); + if (curDevice != null) { + deviceType = curDevice.getPort().type(); + deviceAddress = curDevice.getAddress(); + } + } + // do not log metrics if clearing communication device while no communication device + // was selected + if (deviceType != AudioSystem.DEVICE_OUT_DEFAULT) { + new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + + MediaMetrics.SEPARATOR + "setDeviceForCommunication") + .set(MediaMetrics.Property.DEVICE, + AudioSystem.getDeviceName(deviceType)) + .set(MediaMetrics.Property.ADDRESS, deviceAddress) + .set(MediaMetrics.Property.STATE, device != null + ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) + .record(); + } + + final long ident = Binder.clearCallingIdentity(); + boolean status = + mDeviceBroker.setDeviceForCommunication(cb, pid, device, eventSource); + Binder.restoreCallingIdentity(ident); + return status; + } + + /** @see AudioManager#getDeviceForCommunication() */ + public int getDeviceForCommunication() { + final long ident = Binder.clearCallingIdentity(); + AudioDeviceInfo device = mDeviceBroker.getDeviceForCommunication(); + Binder.restoreCallingIdentity(ident); + if (device == null) { + return 0; + } + return device.getId(); + } + + /** @see AudioManager#addOnCommunicationDeviceChangedListener( + * Executor, AudioManager.OnCommunicationDeviceChangedListener) + */ + public void registerCommunicationDeviceDispatcher( + @Nullable ICommunicationDeviceDispatcher dispatcher) { + if (dispatcher == null) { + return; + } + mDeviceBroker.registerCommunicationDeviceDispatcher(dispatcher); + } + + /** @see AudioManager#removeOnCommunicationDeviceChangedListener( + * AudioManager.OnCommunicationDeviceChangedListener) + */ + public void unregisterCommunicationDeviceDispatcher( + @Nullable ICommunicationDeviceDispatcher dispatcher) { + if (dispatcher == null) { + return; + } + mDeviceBroker.unregisterCommunicationDeviceDispatcher(dispatcher); + } + /** @see AudioManager#setSpeakerphoneOn(boolean) */ public void setSpeakerphoneOn(IBinder cb, boolean on) { if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 42820bba164f2..354472da92869 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -707,7 +707,7 @@ public class BtHelper { mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; } else { Log.w(TAG, "requestScoState: connect to " - + getAnonymizedAddress(mBluetoothHeadsetDevice) + + mBluetoothHeadsetDevice + " failed, mScoAudioMode=" + mScoAudioMode); broadcastScoConnectionState( AudioManager.SCO_AUDIO_STATE_DISCONNECTED); From d88b1e45b6aca9d297f2c556fa98bf6a7c24795a Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Tue, 24 Nov 2020 10:16:45 +0100 Subject: [PATCH 3/3] Audio: use preferred device for strategy for communication route Replaced use of forced usage for communication by set preferred device for strategy to control communication route in audio policy manager. Also do not use forced usage for record which was redundant with forced usage for communication. Bug: 161358428 Test: audio smoke tests Change-Id: I665257fc5903b663096104b0424781d9799635a5 Merged-In: I665257fc5903b663096104b0424781d9799635a5 --- core/jni/android_media_AudioSystem.cpp | 28 ++-- .../android/media/AudioDeviceAttributes.java | 14 +- media/java/android/media/AudioSystem.java | 2 +- .../server/audio/AudioDeviceBroker.java | 132 +++++++++++++----- .../server/audio/AudioDeviceInventory.java | 4 + .../android/server/audio/AudioService.java | 6 +- .../com/android/server/audio/BtHelper.java | 53 ++++--- 7 files changed, 164 insertions(+), 75 deletions(-) diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 1ca45fe9f70b1..20e64ffe94712 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -294,20 +294,25 @@ static sp setJniCallback(JNIEnv* env, return old; } -#define check_AudioSystem_Command(status) _check_AudioSystem_Command(__func__, (status)) +#define check_AudioSystem_Command(...) _check_AudioSystem_Command(__func__, __VA_ARGS__) -static int _check_AudioSystem_Command(const char* caller, status_t status) -{ - ALOGE_IF(status, "Command failed for %s: %d", caller, status); +static int _check_AudioSystem_Command(const char *caller, status_t status, + std::vector ignoredErrors = {}) { + int jniStatus = kAudioStatusOk; switch (status) { case DEAD_OBJECT: - return kAudioStatusMediaServerDied; + jniStatus = kAudioStatusMediaServerDied; + break; case NO_ERROR: - return kAudioStatusOk; + break; default: + if (std::find(begin(ignoredErrors), end(ignoredErrors), status) == end(ignoredErrors)) { + jniStatus = kAudioStatusError; + } break; } - return kAudioStatusError; + ALOGE_IF(jniStatus != kAudioStatusOk, "Command failed for %s: %d", caller, status); + return jniStatus; } static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes, @@ -2381,9 +2386,12 @@ static jint android_media_AudioSystem_setDevicesRoleForStrategy(JNIEnv *env, job static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz, jint strategy, jint role) { - return (jint)check_AudioSystem_Command( - AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy, - (device_role_t)role)); + return (jint) + check_AudioSystem_Command(AudioSystem::removeDevicesRoleForStrategy((product_strategy_t) + strategy, + (device_role_t) + role), + {NAME_NOT_FOUND}); } static jint android_media_AudioSystem_getDevicesForRoleAndStrategy(JNIEnv *env, jobject thiz, diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 6c8b50037d3d0..7caac899a6034 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -120,7 +120,13 @@ public final class AudioDeviceAttributes implements Parcelable { mAddress = address; } - /*package*/ AudioDeviceAttributes(int nativeType, @NonNull String address) { + /** + * @hide + * Constructor from internal device type and address + * @param type the internal device type, as defined in {@link AudioSystem} + * @param address the address of the device, or an empty string for devices without one + */ + public AudioDeviceAttributes(int nativeType, @NonNull String address) { mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT; mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType); mAddress = address; @@ -191,10 +197,8 @@ public final class AudioDeviceAttributes implements Parcelable { public String toString() { return new String("AudioDeviceAttributes:" + " role:" + roleToString(mRole) - + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName( - AudioDeviceInfo.convertDeviceTypeToInternalDevice(mType)) - : AudioSystem.getInputDeviceName( - AudioDeviceInfo.convertDeviceTypeToInternalDevice(mType))) + + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType) + : AudioSystem.getInputDeviceName(mNativeType)) + " addr:" + mAddress); } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index ef6ba065f4147..18c8a72b165bb 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1712,7 +1712,7 @@ public class AudioSystem int[] types = new int[devices.size()]; String[] addresses = new String[devices.size()]; for (int i = 0; i < devices.size(); ++i) { - types[i] = AudioDeviceInfo.convertDeviceTypeToInternalDevice(devices.get(i).getType()); + types[i] = devices.get(i).getInternalType(); addresses[i] = devices.get(i).getAddress(); } return setDevicesRoleForStrategy(strategy, role, types, addresses); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index f04c5eb030dba..26f5c4ca1e802 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -16,6 +16,7 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; @@ -34,6 +35,7 @@ import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.ICommunicationDeviceDispatcher; 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; @@ -51,6 +53,7 @@ import android.util.PrintWriterPrinter; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -77,7 +80,8 @@ import java.util.concurrent.atomic.AtomicBoolean; private final @NonNull Context mContext; /** Forced device usage for communications sent to AudioSystem */ - private int mForcedUseForComm; + private AudioDeviceAttributes mPreferredDeviceforComm; + private int mCommunicationStrategyId = -1; // Manages all connected devices, only ever accessed on the message loop private final AudioDeviceInventory mDeviceInventory; @@ -133,10 +137,23 @@ import java.util.concurrent.atomic.AtomicBoolean; init(); } + private void initCommunicationStrategyId() { + List strategies = AudioProductStrategy.getAudioProductStrategies(); + for (AudioProductStrategy strategy : strategies) { + if (strategy.getAudioAttributesForLegacyStreamType(AudioSystem.STREAM_VOICE_CALL) + != null) { + mCommunicationStrategyId = strategy.getId(); + return; + } + } + mCommunicationStrategyId = -1; + } + private void init() { setupMessaging(mContext); - mForcedUseForComm = AudioSystem.FORCE_NONE; + mPreferredDeviceforComm = null; + initCommunicationStrategyId(); } /*package*/ Context getContext() { @@ -221,8 +238,7 @@ import java.util.concurrent.atomic.AtomicBoolean; synchronized (mDeviceStateLock) { AudioDeviceAttributes device = null; if (on) { - device = new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""); } else { CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); if (client == null || !client.requestsSpeakerphone()) { @@ -275,6 +291,10 @@ import java.util.concurrent.atomic.AtomicBoolean; if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "setCommunicationRouteForClient: device: " + device); } + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setCommunicationRouteForClient for pid: " + pid + + " device: " + device + + " from API: " + eventSource)).printLog(TAG)); final boolean wasBtScoRequested = isBluetoothScoRequested(); final boolean wasSpeakerphoneRequested = isSpeakerphoneRequested(); @@ -393,7 +413,11 @@ import java.util.concurrent.atomic.AtomicBoolean; * @return true if speakerphone is active, false otherwise. */ /*package*/ boolean isSpeakerphoneOn() { - return getForcedUseForComm() == AudioSystem.FORCE_SPEAKER; + AudioDeviceAttributes device = getPreferredDeviceForComm(); + if (device == null) { + return false; + } + return device.getInternalType() == AudioSystem.DEVICE_OUT_SPEAKER; } /** @@ -560,7 +584,11 @@ import java.util.concurrent.atomic.AtomicBoolean; * @return true if Bluetooth SCO is active , false otherwise. */ /*package*/ boolean isBluetoothScoOn() { - return getForcedUseForComm() == AudioSystem.FORCE_BT_SCO; + AudioDeviceAttributes device = getPreferredDeviceForComm(); + if (device == null) { + return false; + } + return AudioSystem.DEVICE_OUT_ALL_SCO_SET.contains(device.getInternalType()); } /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { @@ -612,8 +640,8 @@ import java.util.concurrent.atomic.AtomicBoolean; synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - AudioDeviceAttributes device = new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_SCO, ""); + AudioDeviceAttributes device = + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""); setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource); } } @@ -643,10 +671,19 @@ import java.util.concurrent.atomic.AtomicBoolean; return mDeviceInventory.setPreferredDevicesForStrategySync(strategy, devices); } + /*package*/ void postSetPreferredDevicesForStrategy(int strategy, + @NonNull List devices) { + sendILMsgNoDelay(MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY, SENDMSG_REPLACE, strategy, devices); + } + /*package*/ int removePreferredDevicesForStrategySync(int strategy) { return mDeviceInventory.removePreferredDevicesForStrategySync(strategy); } + /*package*/ void postRemovePreferredDevicesForStrategy(int strategy) { + sendIMsgNoDelay(MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_REPLACE, strategy); + } + /*package*/ void registerStrategyPreferredDevicesDispatcher( @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher); @@ -971,9 +1008,12 @@ import java.util.concurrent.atomic.AtomicBoolean; pw.println(" " + prefix + "pid: " + cl.getPid() + " device: " + cl.getDevice() + " cb: " + cl.getBinder()); }); - pw.println("\n" + prefix + "mForcedUseForComm: " - + AudioSystem.forceUseConfigToString(mForcedUseForComm)); - pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid); + pw.println("\n" + prefix + "mPreferredDeviceforComm: " + + mPreferredDeviceforComm); + pw.println(prefix + "mCommunicationStrategyId: " + + mCommunicationStrategyId); + + pw.println("\n" + prefix + "mModeOwnerPid: " + mModeOwnerPid); mBtHelper.dump(pw, prefix); } @@ -1068,6 +1108,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_RESTORE_DEVICES: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { + initCommunicationStrategyId(); mDeviceInventory.onRestoreDevices(); mBtHelper.onAudioServerDiedRestoreA2dp(); onUpdateCommunicationRoute("MSG_RESTORE_DEVICES"); @@ -1277,6 +1318,17 @@ import java.util.concurrent.atomic.AtomicBoolean; final int strategy = msg.arg1; mDeviceInventory.onSaveRemovePreferredDevices(strategy); } break; + case MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY: { + final int strategy = msg.arg1; + final List devices = + (List) msg.obj; + setPreferredDevicesForStrategySync(strategy, devices); + + } break; + case MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY: { + final int strategy = msg.arg1; + removePreferredDevicesForStrategySync(strategy); + } break; case MSG_CHECK_MUTE_MUSIC: checkMessagesMuteMusic(0); break; @@ -1369,7 +1421,8 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38; private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39; - + private static final int MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY = 40; + private static final int MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY = 41; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { @@ -1609,16 +1662,25 @@ import java.util.concurrent.atomic.AtomicBoolean; * @return selected forced usage for communication. */ @GuardedBy("mDeviceStateLock") - private int getForcedUseForComm() { + @Nullable private AudioDeviceAttributes getPreferredDeviceForComm() { boolean btSCoOn = mBluetoothScoOn && mBtHelper.isBluetoothScoOn(); - if (btSCoOn) { - return AudioSystem.FORCE_BT_SCO; + // 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; + } } - if (isSpeakerphoneRequested()) { - return AudioSystem.FORCE_SPEAKER; + AudioDeviceAttributes device = requestedCommunicationDevice(); + if (device == null + || AudioSystem.DEVICE_OUT_ALL_SCO_SET.contains(device.getInternalType())) { + // Do not indicate BT SCO selection if SCO is requested but SCO is not ON + return null; } - return AudioSystem.FORCE_NONE; + return device; } /** @@ -1628,30 +1690,24 @@ import java.util.concurrent.atomic.AtomicBoolean; // @GuardedBy("mSetModeLock") @GuardedBy("mDeviceStateLock") private void onUpdateCommunicationRoute(String eventSource) { - mForcedUseForComm = getForcedUseForComm(); - + mPreferredDeviceforComm = getPreferredDeviceForComm(); if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "onUpdateCommunicationRoute, mForcedUseForComm: " + mForcedUseForComm - + " eventSource: " + eventSource); + Log.v(TAG, "onUpdateCommunicationRoute, mPreferredDeviceforComm: " + + mPreferredDeviceforComm + " eventSource: " + eventSource); } - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - AudioSystem.setParameters("BT_SCO=on"); - setForceUse_Async( - AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_BT_SCO, eventSource); - setForceUse_Async( - AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource); - } else { + if (mPreferredDeviceforComm == null + || !AudioSystem.DEVICE_OUT_ALL_SCO_SET.contains( + mPreferredDeviceforComm.getInternalType())) { AudioSystem.setParameters("BT_SCO=off"); - setForceUse_Async( - AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); - if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { - setForceUse_Async( - AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_SPEAKER, eventSource); - } else { - setForceUse_Async( - AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_NONE, eventSource); - } + } else { + AudioSystem.setParameters("BT_SCO=on"); + } + if (mPreferredDeviceforComm == null) { + postRemovePreferredDevicesForStrategy(mCommunicationStrategyId); + } else { + postSetPreferredDevicesForStrategy( + mCommunicationStrategyId, Arrays.asList(mPreferredDeviceforComm)); } mAudioService.postUpdateRingerModeServiceInt(); dispatchCommunicationDevice(); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 33a8a30243de0..82586b8f9b235 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -648,6 +648,10 @@ public class AudioDeviceInventory { /*package*/ int setPreferredDevicesForStrategySync(int strategy, @NonNull List devices) { final long identity = Binder.clearCallingIdentity(); + + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setPreferredDevicesForStrategySync, strategy: " + strategy + + " devices: " + devices)).printLog(TAG)); final int status = mAudioSystem.setDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 88b12253d723a..024dca7e23c6e 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4972,8 +4972,7 @@ public class AudioService extends IAudioService.Stub switch (mPlatformType) { case AudioSystem.PLATFORM_VOICE: if (isInCommunication()) { - if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) - == AudioSystem.FORCE_BT_SCO) { + if (mDeviceBroker.isBluetoothScoOn()) { // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO..."); return AudioSystem.STREAM_BLUETOOTH_SCO; } else { @@ -5009,8 +5008,7 @@ public class AudioService extends IAudioService.Stub } default: if (isInCommunication()) { - if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) - == AudioSystem.FORCE_BT_SCO) { + if (mDeviceBroker.isBluetoothScoOn()) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO"); return AudioSystem.STREAM_BLUETOOTH_SCO; } else { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 354472da92869..c9a1fcf76f5b7 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -27,6 +27,7 @@ import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.Intent; +import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Binder; @@ -530,46 +531,64 @@ public class BtHelper { mDeviceBroker.postBroadcastScoConnectionState(state); } - private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { - if (btDevice == null) { - return true; + @Nullable AudioDeviceAttributes getHeadsetAudioDevice() { + if (mBluetoothHeadsetDevice == null) { + return null; } + return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice); + } + + private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } BluetoothClass btClass = btDevice.getBluetoothClass(); - int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - int[] outDeviceTypes = { - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT - }; + int nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; if (btClass != null) { switch (btClass.getDeviceClass()) { case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; + nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; break; case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; + nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; break; } } - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "btHeadsetDeviceToAudioDevice btDevice: " + btDevice + + " btClass: " + (btClass == null ? "Unknown" : btClass) + + " nativeType: " + nativeType + " address: " + address); } + return new AudioDeviceAttributes(nativeType, address); + } + + private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { + if (btDevice == null) { + return true; + } + int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; + AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); String btDeviceName = getName(btDevice); boolean result = false; if (isActive) { - result |= mDeviceBroker.handleDeviceConnection( - isActive, outDeviceTypes[0], address, btDeviceName); + result |= mDeviceBroker.handleDeviceConnection(isActive, audioDevice.getInternalType(), + audioDevice.getAddress(), btDeviceName); } else { + int[] outDeviceTypes = { + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT + }; for (int outDeviceType : outDeviceTypes) { result |= mDeviceBroker.handleDeviceConnection( - isActive, outDeviceType, address, btDeviceName); + isActive, outDeviceType, audioDevice.getAddress(), btDeviceName); } } // handleDeviceConnection() && result to make sure the method get executed result = mDeviceBroker.handleDeviceConnection( - isActive, inDevice, address, btDeviceName) && result; + isActive, inDevice, audioDevice.getAddress(), btDeviceName) && result; return result; }