Loading core/api/current.txt +9 −0 Original line number Diff line number Diff line Loading @@ -19670,15 +19670,18 @@ 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 public int getAudioHwSyncForSession(int); 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(); Loading Loading @@ -19714,11 +19717,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); Loading Loading @@ -19856,6 +19861,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(); } core/api/test-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -988,6 +988,7 @@ package android.media { } public class AudioManager { method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int); method public boolean hasRegisteredDynamicPolicy(); } Loading media/java/android/media/AudioManager.java +314 −0 Original line number Diff line number Diff line Loading @@ -6182,6 +6182,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. Loading Loading @@ -6869,6 +6892,297 @@ public class AudioManager { return hwSyncId; } /** * 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 //-------------------- Loading media/java/android/media/IAudioService.aidl +10 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,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; Loading Loading @@ -338,4 +339,13 @@ interface IAudioService { boolean isMusicActive(in boolean remotely); int getDevicesForStream(in int streamType); boolean setDeviceForCommunication(IBinder cb, int portId); int getDeviceForCommunication(); void registerCommunicationDeviceDispatcher(ICommunicationDeviceDispatcher dispatcher); oneway void unregisterCommunicationDeviceDispatcher( ICommunicationDeviceDispatcher dispatcher); } media/java/android/media/ICommunicationDeviceDispatcher.aidl 0 → 100644 +28 −0 Original line number Diff line number Diff line /* * 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); } Loading
core/api/current.txt +9 −0 Original line number Diff line number Diff line Loading @@ -19670,15 +19670,18 @@ 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 public int getAudioHwSyncForSession(int); 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(); Loading Loading @@ -19714,11 +19717,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); Loading Loading @@ -19856,6 +19861,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(); }
core/api/test-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -988,6 +988,7 @@ package android.media { } public class AudioManager { method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int); method public boolean hasRegisteredDynamicPolicy(); } Loading
media/java/android/media/AudioManager.java +314 −0 Original line number Diff line number Diff line Loading @@ -6182,6 +6182,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. Loading Loading @@ -6869,6 +6892,297 @@ public class AudioManager { return hwSyncId; } /** * 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 //-------------------- Loading
media/java/android/media/IAudioService.aidl +10 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,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; Loading Loading @@ -338,4 +339,13 @@ interface IAudioService { boolean isMusicActive(in boolean remotely); int getDevicesForStream(in int streamType); boolean setDeviceForCommunication(IBinder cb, int portId); int getDeviceForCommunication(); void registerCommunicationDeviceDispatcher(ICommunicationDeviceDispatcher dispatcher); oneway void unregisterCommunicationDeviceDispatcher( ICommunicationDeviceDispatcher dispatcher); }
media/java/android/media/ICommunicationDeviceDispatcher.aidl 0 → 100644 +28 −0 Original line number Diff line number Diff line /* * 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); }