summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt9
-rw-r--r--core/api/test-current.txt1
-rwxr-xr-xmedia/java/android/media/AudioManager.java314
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl10
-rw-r--r--media/java/android/media/ICommunicationDeviceDispatcher.aidl28
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java94
-rwxr-xr-xservices/core/java/com/android/server/audio/AudioService.java110
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java2
8 files changed, 562 insertions, 6 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 74d313c636b7..65d3e4a36b9d 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<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
method public int getAllowedCapturePolicy();
+ method @Nullable public android.media.AudioDeviceInfo getDeviceForCommunication();
method public android.media.AudioDeviceInfo[] getDevices(int);
method public java.util.List<android.media.MicrophoneInfo> 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 b6bd687a3a88..9cf9ce45602b 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 ed9e5175fb78..7dff0c2b9380 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6124,6 +6124,29 @@ public class AudioManager {
}
/**
+ * 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.
* @param callback The {@link AudioDeviceCallback} object to receive connect/disconnect
@@ -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.
+ * <p>The device selection is expressed as an {@link AudioDeviceInfo}, of role sink
+ * ({@link AudioDeviceInfo#isSink()} is <code>true</code>) and of one of the following types:
+ * <ul>
+ * <li> {@link AudioDeviceInfo#TYPE_BUILTIN_EARPIECE}
+ * <li> {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}
+ * <li> {@link AudioDeviceInfo#TYPE_WIRED_HEADSET}
+ * <li> {@link AudioDeviceInfo#TYPE_BLUETOOTH_SCO}
+ * <li> {@link AudioDeviceInfo#TYPE_USB_HEADSET}
+ * <li> {@link AudioDeviceInfo#TYPE_BLE_HEADSET}
+ * </ul>
+ * 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.
+ * <p>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 <code>MODE_IN_CALL</code> can only be selected by the main
+ * telephony application with permission
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE}.
+ * <p> If the requested devices is not currently available, the request will be rejected and
+ * the method will return false.
+ * <p>This API replaces the following deprecated APIs:
+ * <ul>
+ * <li> {@link #startBluetoothSco()}
+ * <li> {@link #stopBluetoothSco()}
+ * <li> {@link #setSpeakerphoneOn(boolean)}
+ * </ul>
+ * <h4>Example</h4>
+ * <p>The example below shows how to enable and disable speakerphone mode.
+ * <pre class="prettyprint">
+ * // 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.
+ * }
+ * </pre>
+ * @param device the requested audio device.
+ * @return <code>true</code> if the request was accepted, <code>false</code> 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.
+ * <p>This API replaces the following deprecated APIs:
+ * <ul>
+ * <li> {@link #isBluetoothScoOn()}
+ * <li> {@link #isSpeakerphoneOn()}
+ * </ul>
+ * @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 <code>AudioDeviceInfo</code> 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 <code>AudioDeviceInfo</code>
+ * 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<CommDevListenerInfo> 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<CommDevListenerInfo> commDevListeners;
+ synchronized (mCommDevListenerLock) {
+ if (mCommDevListeners == null || mCommDevListeners.size() == 0) {
+ return;
+ }
+ commDevListeners = (ArrayList<CommDevListenerInfo>) 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 d9b44cdd20e7..ebaa3162d0e4 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 000000000000..429f934a77dc
--- /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 3b407f14297e..f04c5eb030db 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);
@@ -325,6 +360,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
/**
+ * 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.
* @return true if speakerphone ON requested, false otherwise.
@@ -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<ICommunicationDeviceDispatcher> mCommDevDispatchers =
+ new RemoteCallbackList<ICommunicationDeviceDispatcher>();
+
+ // portId of the device currently selected for communication: avoids broadcasting changes
+ // when same communication route is applied
+ @GuardedBy("mDeviceStateLock")
+ int mCurCommunicationPortId = -1;
+
+ @GuardedBy("mDeviceStateLock")
+ private void dispatchCommunicationDevice() {
+ AudioDeviceInfo device = 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 98c78172431e..2621e6994599 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 42820bba164f..354472da9286 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);