diff options
| author | 2020-05-14 12:45:18 -0700 | |
|---|---|---|
| committer | 2020-05-15 16:15:23 -0700 | |
| commit | 3aad0adc9f9586f3f1785c8a668e51e52d78ca55 (patch) | |
| tree | d05e4c1a518982d7b0823bc19b86be083fd237e0 | |
| parent | d5e4aa1ad72df2b23c1a151aa5338e123e0d476d (diff) | |
AudioService AudioDeviceBroker: fix speakerphone control
Add a list of clients for speakerphone mode requests similar
to Bluetooth SCO clients. This allows to keep track of requests
by different apps in case of overlap or concurrency and to apply
the most relevant mode according to current audio mode owner.
Also:
- Restore requested speakerphone mode when SCO audio is disconnected
instead of returning to earpiece.
- Remove special check on permission while in call in
AudioService.setSpeakerphoneOn because the priority is now
managed by AudioDeviceBroker based on audio mode owner (the owner
for MODE_IN_CALL must have the modify phone state permission).
- Fix condition in AudioService.setMode() preventing from changing
mode while in call. Now allows releasing mode to NORMAL or
reapplying the same mode and just change mode owner.
- Add more information in dumpsys for AudioDeviceBroker and BtHelper.
Bug: 154464603
Test: test transitions between cell call and VoIP calls
Test: Test regressions with calls in speakerphone mode and Bluetooth
Test: AudioManagerTest#testRouting, NoAudioPermissionTest#testRouting
Change-Id: I0d288acf2373c96d52eb91a6ab7142cc3535c719
5 files changed, 231 insertions, 56 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8ea68833e20d..ea7a556f835b 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1505,7 +1505,7 @@ public class AudioManager { public void setSpeakerphoneOn(boolean on){ final IAudioService service = getService(); try { - service.setSpeakerphoneOn(on); + service.setSpeakerphoneOn(mICallBack, on); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index e3b67f86a367..8137275da76a 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -150,7 +150,7 @@ interface IAudioService { oneway void avrcpSupportsAbsoluteVolume(String address, boolean support); - void setSpeakerphoneOn(boolean on); + void setSpeakerphoneOn(IBinder cb, boolean on); boolean isSpeakerphoneOn(); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 3ee3d504d4c1..df4c269d6afa 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -36,6 +36,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.PrintWriterPrinter; @@ -43,6 +44,9 @@ import android.util.PrintWriterPrinter; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.NoSuchElementException; + /** @hide */ /*package*/ final class AudioDeviceBroker { @@ -91,6 +95,9 @@ import java.io.PrintWriter; // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055 /*package*/ final Object mSetModeLock = new Object(); + /** PID of current audio mode owner communicated by AudioService */ + private int mModeOwnerPid = 0; + //------------------------------------------------------------------- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { mContext = context; @@ -136,6 +143,7 @@ import java.io.PrintWriter; /*package*/ void onSystemReady() { synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { + mModeOwnerPid = mAudioService.getModeOwnerPid(); mBtHelper.onSystemReady(); } } @@ -202,28 +210,55 @@ import java.io.PrintWriter; * @param eventSource for logging purposes * @return true if speakerphone state changed */ - /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) { + /*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { synchronized (mDeviceStateLock) { + if (!addSpeakerphoneClient(cb, pid, on)) { + return false; + } final boolean wasOn = isSpeakerphoneOn(); - if (on) { - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); - } - mForcedUseForComm = AudioSystem.FORCE_SPEAKER; - } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { - if (mBtHelper.isBluetoothScoOn()) { - mForcedUseForComm = AudioSystem.FORCE_BT_SCO; - setForceUse_Async( - AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource); - } else { - mForcedUseForComm = AudioSystem.FORCE_NONE; - } + updateSpeakerphoneOn(eventSource); + return (wasOn != isSpeakerphoneOn()); + } + } + + @GuardedBy("mDeviceStateLock") + private void updateSpeakerphoneOn(String eventSource) { + if (isSpeakerphoneOnRequested()) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { + if (mBtHelper.isBluetoothScoOn()) { + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + setForceUse_Async( + AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource); + } else { + mForcedUseForComm = AudioSystem.FORCE_NONE; } + } + mForcedUseForCommExt = mForcedUseForComm; + setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + } - mForcedUseForCommExt = mForcedUseForComm; - setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); - return (wasOn != isSpeakerphoneOn()); + /** + * Returns if speakerphone is requested ON or OFF. + * If the current audio mode owner is in the speakerphone 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 + */ + @GuardedBy("mDeviceStateLock") + private boolean isSpeakerphoneOnRequested() { + if (mSpeakerphoneClients.isEmpty()) { + return false; } + for (SpeakerphoneClient cl : mSpeakerphoneClients) { + if (cl.getPid() == mModeOwnerPid) { + return cl.isOn(); + } + } + return mSpeakerphoneClients.get(0).isOn(); } /*package*/ boolean isSpeakerphoneOn() { @@ -384,7 +419,8 @@ import java.io.PrintWriter; } mForcedUseForComm = AudioSystem.FORCE_BT_SCO; } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - mForcedUseForComm = AudioSystem.FORCE_NONE; + mForcedUseForComm = isSpeakerphoneOnRequested() + ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE; } mForcedUseForCommExt = mForcedUseForComm; AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off")); @@ -429,8 +465,8 @@ import java.io.PrintWriter; sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType); } - /*package*/ void postDisconnectBluetoothSco(int exceptPid) { - sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid); + /*package*/ void postSetModeOwnerPid(int pid) { + sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid); } /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { @@ -483,7 +519,7 @@ import java.io.PrintWriter; } /*package*/ int getModeOwnerPid() { - return mAudioService.getModeOwnerPid(); + return mModeOwnerPid; } /*package*/ int getDeviceForStream(int streamType) { @@ -605,6 +641,10 @@ import java.io.PrintWriter; sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj); } + /*package*/ void postSpeakerphoneClientDied(Object obj) { + sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj); + } + /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDeviceAttributes device) { @@ -674,7 +714,7 @@ import java.io.PrintWriter; new BtHelper.BluetoothA2dpDeviceInfo(btDevice); return (mBrokerHandler.hasEqualMessages( MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, devInfoToCheck) - || mBrokerHandler.hasEqualMessages( + || mBrokerHandler.hasEqualMessages( MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, devInfoToCheck)); } @@ -707,7 +747,20 @@ import java.io.PrintWriter; } else { pw.println("Message handler is null"); } + mDeviceInventory.dump(pw, prefix); + + 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); } //--------------------------------------------------------------------- @@ -877,10 +930,16 @@ import java.io.PrintWriter; mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); } break; - case MSG_I_DISCONNECT_BT_SCO: + case MSG_I_SET_MODE_OWNER_PID: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - mBtHelper.disconnectBluetoothSco(msg.arg1); + if (mModeOwnerPid != msg.arg1) { + mModeOwnerPid = msg.arg1; + updateSpeakerphoneOn("setNewModeOwner"); + if (mModeOwnerPid != 0) { + mBtHelper.disconnectBluetoothSco(mModeOwnerPid); + } + } } } break; @@ -891,6 +950,11 @@ import java.io.PrintWriter; } } break; + case MSG_L_SPEAKERPHONE_CLIENT_DIED: + synchronized (mDeviceStateLock) { + speakerphoneClientDied(msg.obj); + } + break; case MSG_TOGGLE_HDMI: synchronized (mDeviceStateLock) { mDeviceInventory.onToggleHdmi(); @@ -1021,7 +1085,7 @@ import java.io.PrintWriter; private static final int MSG_REPORT_NEW_ROUTES = 13; private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; - private static final int MSG_I_DISCONNECT_BT_SCO = 16; + private static final int MSG_I_SET_MODE_OWNER_PID = 16; // process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18; @@ -1051,6 +1115,8 @@ import java.io.PrintWriter; private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33; private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34; + private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { @@ -1166,4 +1232,93 @@ import java.io.PrintWriter; time); } } + + private class SpeakerphoneClient implements IBinder.DeathRecipient { + private final IBinder mCb; + private final int mPid; + private final boolean mOn; + SpeakerphoneClient(IBinder cb, int pid, boolean on) { + mCb = cb; + mPid = pid; + mOn = on; + } + + public boolean registerDeathRecipient() { + boolean status = false; + try { + mCb.linkToDeath(this, 0); + status = true; + } catch (RemoteException e) { + Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death"); + } + return status; + } + + public void unregisterDeathRecipient() { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "SpeakerphoneClient could not not unregistered to binder"); + } + } + + @Override + public void binderDied() { + postSpeakerphoneClientDied(this); + } + + IBinder getBinder() { + return mCb; + } + + int getPid() { + return mPid; + } + + boolean isOn() { + return mOn; + } + } + + @GuardedBy("mDeviceStateLock") + private void speakerphoneClientDied(Object obj) { + if (obj == null) { + return; + } + Log.w(TAG, "Speaker client died"); + if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) { + updateSpeakerphoneOn("speakerphoneClientDied"); + } + } + + private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) { + for (SpeakerphoneClient cl : mSpeakerphoneClients) { + if (cl.getBinder() == cb) { + if (unregister) { + cl.unregisterDeathRecipient(); + } + mSpeakerphoneClients.remove(cl); + return cl; + } + } + return null; + } + + @GuardedBy("mDeviceStateLock") + private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) { + // always insert new request at first position + removeSpeakerphoneClient(cb, true); + SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on); + if (client.registerDeathRecipient()) { + mSpeakerphoneClients.add(0, client); + return true; + } + return false; + } + + // List of clients requesting speakerPhone ON + @GuardedBy("mDeviceStateLock") + private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients = + new ArrayList<SpeakerphoneClient>(); + } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 8068e378f2e3..2407fa96f719 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3594,11 +3594,9 @@ public class AudioService extends IAudioService.Stub } public void binderDied() { - int oldModeOwnerPid; int newModeOwnerPid = 0; synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); - oldModeOwnerPid = getModeOwnerPid(); int index = mSetModeDeathHandlers.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered setMode() client died"); @@ -3608,9 +3606,7 @@ public class AudioService extends IAudioService.Stub } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes - if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); - } + mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid); } public int getPid() { @@ -3662,17 +3658,20 @@ public class AudioService extends IAudioService.Stub return; } - int oldModeOwnerPid; int newModeOwnerPid; synchronized (mDeviceBroker.mSetModeLock) { if (mode == AudioSystem.MODE_CURRENT) { mode = mMode; } - oldModeOwnerPid = getModeOwnerPid(); + int oldModeOwnerPid = getModeOwnerPid(); // Do not allow changing mode if a call is active and the requester - // does not have permission to modify phone state or is not the mode owner. - if (((mMode == AudioSystem.MODE_IN_CALL) - || (mMode == AudioSystem.MODE_IN_COMMUNICATION)) + // does not have permission to modify phone state or is not the mode owner, + // unless returning to NORMAL mode (will not change current mode owner) or + // not changing mode in which case the mode owner will reflect the last + // requester of current mode + if (!((mode == mMode) || (mode == AudioSystem.MODE_NORMAL)) + && ((mMode == AudioSystem.MODE_IN_CALL) + || (mMode == AudioSystem.MODE_IN_COMMUNICATION)) && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) { Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid + ", uid=" + Binder.getCallingUid() @@ -3685,9 +3684,7 @@ public class AudioService extends IAudioService.Stub } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes - if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); - } + mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid); } // setModeInt() returns a valid PID if the audio mode was successfully set to @@ -3964,32 +3961,18 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setSpeakerphoneOn(boolean) */ - public void setSpeakerphoneOn(boolean on){ + public void setSpeakerphoneOn(IBinder cb, boolean on) { if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) - != PackageManager.PERMISSION_GRANTED) { - synchronized (mSetModeDeathHandlers) { - for (SetModeDeathHandler h : mSetModeDeathHandlers) { - if (h.getMode() == AudioSystem.MODE_IN_CALL) { - Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); - return; - } - } - } - } - // 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(on, eventSource); + final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource); new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR + "setSpeakerphoneOn") .setUid(uid) diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index b6ffcef8fef0..b4c41b274dbe 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -38,6 +38,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; @@ -131,6 +132,26 @@ public class BtHelper { } } + /** + * Returns a string representation of the scoAudioState. + */ + public static String scoAudioStateToString(int scoAudioState) { + switch (scoAudioState) { + case SCO_STATE_INACTIVE: + return "SCO_STATE_INACTIVE"; + case SCO_STATE_ACTIVATE_REQ: + return "SCO_STATE_ACTIVATE_REQ"; + case SCO_STATE_ACTIVE_EXTERNAL: + return "SCO_STATE_ACTIVE_EXTERNAL"; + case SCO_STATE_ACTIVE_INTERNAL: + return "SCO_STATE_ACTIVE_INTERNAL"; + case SCO_STATE_DEACTIVATING: + return "SCO_STATE_DEACTIVATING"; + default: + return "SCO_STATE_(" + scoAudioState + ")"; + } + } + //---------------------------------------------------------------------- /*package*/ static class BluetoothA2dpDeviceInfo { private final @NonNull BluetoothDevice mBtDevice; @@ -1007,4 +1028,20 @@ public class BtHelper { return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")"; } } + + //------------------------------------------------------------ + /*package*/ void dump(PrintWriter pw, String prefix) { + pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset); + 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); + } + } |