diff options
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 e5a7daf9d1d3..98d662a1a9b5 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3567,11 +3567,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"); @@ -3581,9 +3579,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() { @@ -3635,17 +3631,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() @@ -3658,9 +3657,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 @@ -3937,32 +3934,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); + } + } |