diff options
-rw-r--r-- | core/api/current.txt | 9 | ||||
-rw-r--r-- | core/api/test-current.txt | 1 | ||||
-rwxr-xr-x | media/java/android/media/AudioManager.java | 314 | ||||
-rwxr-xr-x | media/java/android/media/IAudioService.aidl | 10 | ||||
-rw-r--r-- | media/java/android/media/ICommunicationDeviceDispatcher.aidl | 28 | ||||
-rw-r--r-- | services/core/java/com/android/server/audio/AudioDeviceBroker.java | 94 | ||||
-rwxr-xr-x | services/core/java/com/android/server/audio/AudioService.java | 110 | ||||
-rw-r--r-- | services/core/java/com/android/server/audio/BtHelper.java | 2 |
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 569640ae459d..88b12253d723 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); |