diff --git a/core/api/current.txt b/core/api/current.txt index 74d313c636b75..65d3e4a36b9de 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 getActivePlaybackConfigurations(); method @NonNull public java.util.List getActiveRecordingConfigurations(); method public int getAllowedCapturePolicy(); + method @Nullable public android.media.AudioDeviceInfo getDeviceForCommunication(); method public android.media.AudioDeviceInfo[] getDevices(int); method public java.util.List 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 b6bd687a3a88c..9cf9ce45602b8 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 ed9e5175fb781..7dff0c2b93806 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6123,6 +6123,29 @@ public class AudioManager { return infoListFromPortList(ports, flags); } + /** + * 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. @@ -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. + *

The device selection is expressed as an {@link AudioDeviceInfo}, of role sink + * ({@link AudioDeviceInfo#isSink()} is true) and of one of the following types: + *

    + *
  • {@link AudioDeviceInfo#TYPE_BUILTIN_EARPIECE} + *
  • {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER} + *
  • {@link AudioDeviceInfo#TYPE_WIRED_HEADSET} + *
  • {@link AudioDeviceInfo#TYPE_BLUETOOTH_SCO} + *
  • {@link AudioDeviceInfo#TYPE_USB_HEADSET} + *
  • {@link AudioDeviceInfo#TYPE_BLE_HEADSET} + *
+ * 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. + *

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 MODE_IN_CALL can only be selected by the main + * telephony application with permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE}. + *

If the requested devices is not currently available, the request will be rejected and + * the method will return false. + *

This API replaces the following deprecated APIs: + *

    + *
  • {@link #startBluetoothSco()} + *
  • {@link #stopBluetoothSco()} + *
  • {@link #setSpeakerphoneOn(boolean)} + *
+ *

Example

+ *

The example below shows how to enable and disable speakerphone mode. + *

+     * // 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.
+     * }
+     * 
+ * @param device the requested audio device. + * @return true if the request was accepted, false 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. + *

This API replaces the following deprecated APIs: + *

    + *
  • {@link #isBluetoothScoOn()} + *
  • {@link #isSpeakerphoneOn()} + *
+ * @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 AudioDeviceInfo 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 AudioDeviceInfo + * 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 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 commDevListeners; + synchronized (mCommDevListenerLock) { + if (mCommDevListeners == null || mCommDevListeners.size() == 0) { + return; + } + commDevListeners = (ArrayList) 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 d9b44cdd20e75..ebaa3162d0e46 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 0000000000000..429f934a77dc1 --- /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 3b407f14297ea..f04c5eb030dba 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); @@ -324,6 +359,20 @@ import java.util.concurrent.atomic.AtomicBoolean; return device; } + /** + * 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. @@ -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 mCommDevDispatchers = + new RemoteCallbackList(); + + // 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 569640ae459d8..88b12253d723a 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 42820bba164f2..354472da92869 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);