summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xmedia/java/android/media/AudioManager.java2
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java205
-rwxr-xr-xservices/core/java/com/android/server/audio/AudioService.java41
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java37
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);
+ }
+
}