diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index c6622238a7921..e6cba66039073 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -40,6 +40,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.AudioAttributes.AttributeSystemUsage; +import android.media.CallbackUtil.ListenerInfo; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; import android.media.audiopolicy.AudioProductStrategy; @@ -2900,79 +2901,33 @@ public class AudioManager { * List is lazy-initialized on first registration */ @GuardedBy("mModeListenerLock") - private @Nullable ArrayList mModeListeners; + private @Nullable ArrayList> mModeListeners; @GuardedBy("mModeListenerLock") private ModeDispatcherStub mModeDispatcherStub; - private final class ModeDispatcherStub - extends IAudioModeDispatcher.Stub { + private final class ModeDispatcherStub extends IAudioModeDispatcher.Stub { + + public void register(boolean register) { + try { + if (register) { + getService().registerModeDispatcher(this); + } else { + getService().unregisterModeDispatcher(this); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchAudioModeChanged(int mode) { - // make a shallow copy of listeners so callback is not executed under lock - final ArrayList modeListeners; - synchronized (mModeListenerLock) { - if (mModeListeners == null || mModeListeners.size() == 0) { - return; - } - modeListeners = (ArrayList) mModeListeners.clone(); - } - final long ident = Binder.clearCallingIdentity(); - try { - for (ModeListenerInfo info : modeListeners) { - info.mExecutor.execute(() -> - info.mListener.onModeChanged(mode)); - } - } finally { - Binder.restoreCallingIdentity(ident); - } + CallbackUtil.callListeners(mModeListeners, mModeListenerLock, + (listener) -> listener.onModeChanged(mode)); } } - private static class ModeListenerInfo { - final @NonNull OnModeChangedListener mListener; - final @NonNull Executor mExecutor; - - ModeListenerInfo(OnModeChangedListener listener, Executor exe) { - mListener = listener; - mExecutor = exe; - } - } - - @GuardedBy("mModeListenerLock") - private boolean hasModeListener(OnModeChangedListener listener) { - return getModeListenerInfo(listener) != null; - } - - @GuardedBy("mModeListenerLock") - private @Nullable ModeListenerInfo getModeListenerInfo( - OnModeChangedListener listener) { - if (mModeListeners == null) { - return null; - } - for (ModeListenerInfo info : mModeListeners) { - if (info.mListener == listener) { - return info; - } - } - return null; - } - - - @GuardedBy("mModeListenerLock") - /** - * @return true if the listener was removed from the list - */ - private boolean removeModeListener(OnModeChangedListener listener) { - final ModeListenerInfo infoToRemove = getModeListenerInfo(listener); - if (infoToRemove != null) { - mModeListeners.remove(infoToRemove); - return true; - } - return false; - } - /** * Adds a listener to be notified of changes to the audio mode. * See {@link #getMode()} @@ -2982,30 +2937,14 @@ public class AudioManager { public void addOnModeChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnModeChangedListener listener) { - Objects.requireNonNull(executor); - Objects.requireNonNull(listener); synchronized (mModeListenerLock) { - if (hasModeListener(listener)) { - throw new IllegalArgumentException("attempt to call addOnModeChangedListener() " - + "on a previously registered listener"); - } - // lazy initialization of the list of strategy-preferred device listener - if (mModeListeners == null) { - mModeListeners = new ArrayList<>(); - } - final int oldCbCount = mModeListeners.size(); - mModeListeners.add(new ModeListenerInfo(listener, executor)); - if (oldCbCount == 0) { - // register binder for callbacks - if (mModeDispatcherStub == null) { - mModeDispatcherStub = new ModeDispatcherStub(); - } - try { - getService().registerModeDispatcher(mModeDispatcherStub); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + final Pair>, ModeDispatcherStub> res = + CallbackUtil.addListener("addOnModeChangedListener", + executor, listener, mModeListeners, mModeDispatcherStub, + () -> new ModeDispatcherStub(), + stub -> stub.register(true)); + mModeListeners = res.first; + mModeDispatcherStub = res.second; } } @@ -3015,23 +2954,13 @@ public class AudioManager { * @param listener */ public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) { - Objects.requireNonNull(listener); synchronized (mModeListenerLock) { - if (!removeModeListener(listener)) { - throw new IllegalArgumentException("attempt to call removeOnModeChangedListener() " - + "on an unregistered listener"); - } - if (mModeListeners.size() == 0) { - // unregister binder for callbacks - try { - getService().unregisterModeDispatcher(mModeDispatcherStub); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mModeDispatcherStub = null; - mModeListeners = null; - } - } + final Pair>, ModeDispatcherStub> res = + CallbackUtil.removeListener("removeOnModeChangedListener", + listener, mModeListeners, mModeDispatcherStub, + stub -> stub.register(false)); + mModeListeners = res.first; + mModeDispatcherStub = res.second; } } @@ -7646,31 +7575,15 @@ public class AudioManager { 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(); - } - } + final Pair>, + CommunicationDeviceDispatcherStub> res = + CallbackUtil.addListener("addOnCommunicationDeviceChangedListener", + executor, listener, mCommDevListeners, mCommDevDispatcherStub, + () -> new CommunicationDeviceDispatcherStub(), + stub -> stub.register(true)); + mCommDevListeners = res.first; + mCommDevDispatcherStub = res.second; } } @@ -7681,25 +7594,14 @@ public class AudioManager { */ 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; - } - } + final Pair>, + CommunicationDeviceDispatcherStub> res = + CallbackUtil.removeListener("removeOnCommunicationDeviceChangedListener", + listener, mCommDevListeners, mCommDevDispatcherStub, + stub -> stub.register(false)); + mCommDevListeners = res.first; + mCommDevDispatcherStub = res.second; } } @@ -7709,17 +7611,8 @@ public class AudioManager { * 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; - } - } + private @Nullable + ArrayList> mCommDevListeners; @GuardedBy("mCommDevListenerLock") private CommunicationDeviceDispatcherStub mCommDevDispatcherStub; @@ -7727,59 +7620,25 @@ public class AudioManager { 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(); + public void register(boolean register) { try { - for (CommDevListenerInfo info : commDevListeners) { - info.mExecutor.execute(() -> - info.mListener.onCommunicationDeviceChanged(device)); + if (register) { + getService().registerCommunicationDeviceDispatcher(this); + } else { + getService().unregisterCommunicationDeviceDispatcher(this); } - } finally { - Binder.restoreCallingIdentity(ident); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } - } - @GuardedBy("mCommDevListenerLock") - private @Nullable CommDevListenerInfo getCommDevListenerInfo( - OnCommunicationDeviceChangedListener listener) { - if (mCommDevListeners == null) { - return null; + @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method + public void dispatchCommunicationDeviceChanged(int portId) { + AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS); + CallbackUtil.callListeners(mCommDevListeners, mCommDevListenerLock, + (listener) -> listener.onCommunicationDeviceChanged(device)); } - 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; } //--------------------------------------------------------- @@ -7838,4 +7697,4 @@ public class AudioManager { return mHandler; } } -} +} \ No newline at end of file diff --git a/media/java/android/media/CallbackUtil.java b/media/java/android/media/CallbackUtil.java new file mode 100644 index 0000000000000..ac39317ed1d5f --- /dev/null +++ b/media/java/android/media/CallbackUtil.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2021 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.permission.ClearCallingIdentityContext; +import android.media.permission.SafeCloseable; +import android.util.Log; +import android.util.Pair; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * @hide + * A utility class to implement callback listeners and their management. + * This is meant to be used for lazily-initialized listener lists and stubs for event reception, + * typically received from server (e.g. AudioService). + */ + +/*package*/ class CallbackUtil { + + private static final String TAG = "CallbackUtil"; + + /** + * Container class to store a listener and associated Executor + * @param the type of the listener + */ + static class ListenerInfo { + final @NonNull T mListener; + final @NonNull Executor mExecutor; + + ListenerInfo(@NonNull T listener, @NonNull Executor exe) { + mListener = listener; + mExecutor = exe; + } + } + + /** + * Finds the listener information (listener + Executor) in a given list of listeners + * @param listener the listener to find + * @param listeners the list of listener informations, can be null if not instantiated yet + * @param the type of the listeners + * @return null if the listener is not in the given list of listener informations + */ + static @Nullable ListenerInfo getListenerInfo( + @NonNull T listener, @Nullable ArrayList> listeners) { + if (listeners == null) { + return null; + } + for (ListenerInfo info : listeners) { + if (info.mListener == listener) { + return info; + } + } + return null; + } + + /** + * Returns true if the given listener is present in the list of listener informations + * @param listener the listener to find + * @param listeners the list of listener informations, can be null if not instantiated yet + * @param the type of the listeners + * @return true if the listener is in the list + */ + static boolean hasListener(@NonNull T listener, + @Nullable ArrayList> listeners) { + return getListenerInfo(listener, listeners) != null; + } + + /** + * Removes the given listener from the list of listener informations + * @param listener the listener to remove + * @param listeners the list of listener informations, can be null if not instantiated yet + * @param the type of the listeners + * @return true if the listener was found and removed from the list, false otherwise + */ + static boolean removeListener(@NonNull T listener, + @Nullable ArrayList> listeners) { + final ListenerInfo infoToRemove = getListenerInfo(listener, listeners); + if (infoToRemove != null) { + listeners.remove(infoToRemove); + return true; + } + return false; + } + + /** + * Adds a listener and associated Executor in the list of listeners. + * This method handles the lazy initialization of both the list of listeners and the stub + * used to receive the events that will be forwarded to the listener, see the returned pair + * for the updated references. + * @param methodName the name of the method calling this, for inclusion in the + * string in case of IllegalArgumentException + * @param executor the Executor for the listener + * @param listener the listener to add + * @param listeners the list of listener informations, can be null if not instantiated yet + * @param dispatchStub the stub that receives the events to be forwarded to the listeners, + * can be null if not instantiated yet + * @param newStub the function to create a new stub if needed + * @param registerStub the function for the stub registration if needed + * @param the type of the listener interface + * @param the type of the event receiver stub + * @return a pair of the listener list and the event receiver stub which may have been + * initialized if needed (e.g. on the first ever addition of a listener) + */ + static Pair>, S> addListener(String methodName, + @NonNull Executor executor, + @NonNull T listener, + @Nullable ArrayList> listeners, + @Nullable S dispatchStub, + @NonNull java.util.function.Supplier newStub, + @NonNull java.util.function.Consumer registerStub) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + + if (hasListener(listener, listeners)) { + throw new IllegalArgumentException("attempt to call " + methodName + + "on a previously registered listener"); + } + // lazy initialization of the list of strategy-preferred device listener + if (listeners == null) { + listeners = new ArrayList<>(); + } + if (listeners.size() == 0) { + // register binder for callbacks + if (dispatchStub == null) { + try { + dispatchStub = newStub.get(); + } catch (Exception e) { + Log.e(TAG, "Exception while creating stub in " + methodName, e); + return new Pair<>(null, null); + } + } + registerStub.accept(dispatchStub); + } + listeners.add(new ListenerInfo(listener, executor)); + return new Pair(listeners, dispatchStub); + } + + /** + * Removes a listener from the list of listeners. + * This method handles the freeing of both the list of listeners and the stub + * used to receive the events that will be forwarded to the listener,see the returned pair + * for the updated references. + * @param methodName the name of the method calling this, for inclusion in the + * string in case of IllegalArgumentException + * @param listener the listener to remove + * @param listeners the list of listener informations, can be null if not instantiated yet + * @param dispatchStub the stub that receives the events to be forwarded to the listeners, + * can be null if not instantiated yet + * @param unregisterStub the function to unregister the stub if needed + * @param the type of the listener interface + * @param the type of the event receiver stub + * @return a pair of the listener list and the event receiver stub which may have been + * changed if needed (e.g. on the removal of the last listener) + */ + static Pair>, S> removeListener(String methodName, + @NonNull T listener, + @Nullable ArrayList> listeners, + @Nullable S dispatchStub, + @NonNull java.util.function.Consumer unregisterStub) { + Objects.requireNonNull(listener); + + if (!removeListener(listener, listeners)) { + throw new IllegalArgumentException("attempt to call " + methodName + + "on an unregistered listener"); + } + if (listeners.size() == 0) { + unregisterStub.accept(dispatchStub); + return new Pair<>(null, null); + } else { + return new Pair<>(listeners, dispatchStub); + } + } + + interface CallbackMethod { + void callbackMethod(T listener); + } + + /** + * Exercise the callback of the listeners + * @param listeners the list of listeners + * @param listenerLock the lock guarding the list of listeners + * @param callback the function to call for each listener + * @param the type of the listener interface + */ + static void callListeners( + @Nullable ArrayList> listeners, + @NonNull Object listenerLock, + @NonNull CallbackMethod callback) { + Objects.requireNonNull(listenerLock); + // make a shallow copy of listeners so callback is not executed under lock + final ArrayList> listenersShallowCopy; + synchronized (listenerLock) { + if (listeners == null || listeners.size() == 0) { + return; + } + listenersShallowCopy = (ArrayList>) listeners.clone(); + } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + for (ListenerInfo info : listenersShallowCopy) { + info.mExecutor.execute(() -> callback.callbackMethod(info.mListener)); + } + } + + } +} diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index e6fff392c2643..c0793ec58e537 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -22,11 +22,14 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.media.CallbackUtil.ListenerInfo; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.SafeCloseable; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.GuardedBy; @@ -373,32 +376,15 @@ public class Spatializer { public void addOnSpatializerStateChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnSpatializerStateChangedListener listener) { - Objects.requireNonNull(executor); - Objects.requireNonNull(listener); synchronized (mStateListenerLock) { - if (hasSpatializerStateListener(listener)) { - throw new IllegalArgumentException( - "Called addOnSpatializerStateChangedListener() " - + "on a previously registered listener"); - } - // lazy initialization of the list of strategy-preferred device listener - if (mStateListeners == null) { - mStateListeners = new ArrayList<>(); - } - mStateListeners.add(new StateListenerInfo(listener, executor)); - if (mStateListeners.size() == 1) { - // register binder for callbacks - if (mInfoDispatcherStub == null) { - mInfoDispatcherStub = - new SpatializerInfoDispatcherStub(); - } - try { - mAm.getService().registerSpatializerCallback( - mInfoDispatcherStub); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + final Pair>, + SpatializerInfoDispatcherStub> res = + CallbackUtil.addListener("addOnSpatializerStateChangedListener", + executor, listener, mStateListeners, mInfoDispatcherStub, + () -> new SpatializerInfoDispatcherStub(), + stub -> stub.register(true)); + mStateListeners = res.first; + mInfoDispatcherStub = res.second; } } @@ -410,24 +396,14 @@ public class Spatializer { */ public void removeOnSpatializerStateChangedListener( @NonNull OnSpatializerStateChangedListener listener) { - Objects.requireNonNull(listener); synchronized (mStateListenerLock) { - if (!removeStateListener(listener)) { - throw new IllegalArgumentException( - "Called removeOnSpatializerStateChangedListener() " - + "on an unregistered listener"); - } - if (mStateListeners.size() == 0) { - // unregister binder for callbacks - try { - mAm.getService().unregisterSpatializerCallback(mInfoDispatcherStub); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mInfoDispatcherStub = null; - mStateListeners = null; - } - } + final Pair>, + SpatializerInfoDispatcherStub> res = + CallbackUtil.removeListener("removeOnSpatializerStateChangedListener", + listener, mStateListeners, mInfoDispatcherStub, + stub -> stub.register(false)); + mStateListeners = res.first; + mInfoDispatcherStub = res.second; } } @@ -489,93 +465,41 @@ public class Spatializer { * List is lazy-initialized on first registration */ @GuardedBy("mStateListenerLock") - private @Nullable ArrayList mStateListeners; + private @Nullable ArrayList> mStateListeners; @GuardedBy("mStateListenerLock") private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub; private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub { + public void register(boolean register) { + try { + if (register) { + mAm.getService().registerSpatializerCallback(this); + } else { + mAm.getService().unregisterSpatializerCallback(this); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerEnabledChanged(boolean enabled) { - // make a shallow copy of listeners so callback is not executed under lock - final ArrayList stateListeners; - synchronized (mStateListenerLock) { - if (mStateListeners == null || mStateListeners.size() == 0) { - return; - } - stateListeners = (ArrayList) mStateListeners.clone(); - } - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - for (StateListenerInfo info : stateListeners) { - info.mExecutor.execute(() -> - info.mListener.onSpatializerEnabledChanged(Spatializer.this, enabled)); - } - } + CallbackUtil.callListeners(mStateListeners, mStateListenerLock, + (listener) -> listener.onSpatializerEnabledChanged( + Spatializer.this, enabled)); } @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerAvailableChanged(boolean available) { - // make a shallow copy of listeners so callback is not executed under lock - final ArrayList stateListeners; - synchronized (mStateListenerLock) { - if (mStateListeners == null || mStateListeners.size() == 0) { - return; - } - stateListeners = (ArrayList) mStateListeners.clone(); - } - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - for (StateListenerInfo info : stateListeners) { - info.mExecutor.execute(() -> - info.mListener.onSpatializerAvailableChanged( - Spatializer.this, available)); - } - } + CallbackUtil.callListeners(mStateListeners, mStateListenerLock, + (listener) -> listener.onSpatializerAvailableChanged( + Spatializer.this, available)); } } - private static class StateListenerInfo { - final @NonNull OnSpatializerStateChangedListener mListener; - final @NonNull Executor mExecutor; - - StateListenerInfo(@NonNull OnSpatializerStateChangedListener listener, - @NonNull Executor exe) { - mListener = listener; - mExecutor = exe; - } - } - - @GuardedBy("mStateListenerLock") - private boolean hasSpatializerStateListener(OnSpatializerStateChangedListener listener) { - return getStateListenerInfo(listener) != null; - } - - @GuardedBy("mStateListenerLock") - private @Nullable StateListenerInfo getStateListenerInfo( - OnSpatializerStateChangedListener listener) { - if (mStateListeners == null) { - return null; - } - for (StateListenerInfo info : mStateListeners) { - if (info.mListener == listener) { - return info; - } - } - return null; - } - - @GuardedBy("mStateListenerLock") - /** - * @return true if the listener was removed from the list - */ - private boolean removeStateListener(OnSpatializerStateChangedListener listener) { - final StateListenerInfo infoToRemove = getStateListenerInfo(listener); - if (infoToRemove != null) { - mStateListeners.remove(infoToRemove); - return true; - } - return false; - } - /** * @hide @@ -688,33 +612,15 @@ public class Spatializer { public void addOnHeadTrackingModeChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnHeadTrackingModeChangedListener listener) { - Objects.requireNonNull(executor); - Objects.requireNonNull(listener); synchronized (mHeadTrackingListenerLock) { - if (hasListener(listener, mHeadTrackingListeners)) { - throw new IllegalArgumentException( - "Called addOnHeadTrackingModeChangedListener() " - + "on a previously registered listener"); - } - // lazy initialization of the list of strategy-preferred device listener - if (mHeadTrackingListeners == null) { - mHeadTrackingListeners = new ArrayList<>(); - } - mHeadTrackingListeners.add( - new ListenerInfo(listener, executor)); - if (mHeadTrackingListeners.size() == 1) { - // register binder for callbacks - if (mHeadTrackingDispatcherStub == null) { - mHeadTrackingDispatcherStub = - new SpatializerHeadTrackingDispatcherStub(); - } - try { - mAm.getService().registerSpatializerHeadTrackingCallback( - mHeadTrackingDispatcherStub); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + final Pair>, + SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.addListener( + "addOnHeadTrackingModeChangedListener", executor, listener, + mHeadTrackingListeners, mHeadTrackingDispatcherStub, + () -> new SpatializerHeadTrackingDispatcherStub(), + stub -> stub.register(true)); + mHeadTrackingListeners = res.first; + mHeadTrackingDispatcherStub = res.second; } } @@ -728,25 +634,14 @@ public class Spatializer { @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void removeOnHeadTrackingModeChangedListener( @NonNull OnHeadTrackingModeChangedListener listener) { - Objects.requireNonNull(listener); synchronized (mHeadTrackingListenerLock) { - if (!removeListener(listener, mHeadTrackingListeners)) { - throw new IllegalArgumentException( - "Called removeOnHeadTrackingModeChangedListener() " - + "on an unregistered listener"); - } - if (mHeadTrackingListeners.size() == 0) { - // unregister binder for callbacks - try { - mAm.getService().unregisterSpatializerHeadTrackingCallback( - mHeadTrackingDispatcherStub); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mHeadTrackingDispatcherStub = null; - mHeadTrackingListeners = null; - } - } + final Pair>, + SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.removeListener( + "removeOnHeadTrackingModeChangedListener", listener, + mHeadTrackingListeners, mHeadTrackingDispatcherStub, + stub -> stub.register(false)); + mHeadTrackingListeners = res.first; + mHeadTrackingDispatcherStub = res.second; } } @@ -930,45 +825,6 @@ public class Spatializer { } } - //----------------------------------------------------------------------------- - // callback helper definitions - - private static class ListenerInfo { - final @NonNull T mListener; - final @NonNull Executor mExecutor; - - ListenerInfo(T listener, Executor exe) { - mListener = listener; - mExecutor = exe; - } - } - - private static ListenerInfo getListenerInfo( - T listener, ArrayList> listeners) { - if (listeners == null) { - return null; - } - for (ListenerInfo info : listeners) { - if (info.mListener == listener) { - return info; - } - } - return null; - } - - private static boolean hasListener(T listener, ArrayList> listeners) { - return getListenerInfo(listener, listeners) != null; - } - - private static boolean removeListener(T listener, ArrayList> listeners) { - final ListenerInfo infoToRemove = getListenerInfo(listener, listeners); - if (infoToRemove != null) { - listeners.remove(infoToRemove); - return true; - } - return false; - } - //----------------------------------------------------------------------------- // head tracking callback management and stub @@ -986,42 +842,31 @@ public class Spatializer { private final class SpatializerHeadTrackingDispatcherStub extends ISpatializerHeadTrackingModeCallback.Stub { - @Override - public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) { - // make a shallow copy of listeners so callback is not executed under lock - final ArrayList> headTrackingListeners; - synchronized (mHeadTrackingListenerLock) { - if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) { - return; - } - headTrackingListeners = (ArrayList>) - mHeadTrackingListeners.clone(); - } - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - for (ListenerInfo info : headTrackingListeners) { - info.mExecutor.execute(() -> info.mListener - .onHeadTrackingModeChanged(Spatializer.this, mode)); + public void register(boolean register) { + try { + if (register) { + mAm.getService().registerSpatializerHeadTrackingCallback(this); + } else { + mAm.getService().unregisterSpatializerHeadTrackingCallback(this); } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method + public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) { + CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock, + (listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode)); + } + + @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) { - // make a shallow copy of listeners so callback is not executed under lock - final ArrayList> headTrackingListeners; - synchronized (mHeadTrackingListenerLock) { - if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) { - return; - } - headTrackingListeners = (ArrayList>) - mHeadTrackingListeners.clone(); - } - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - for (ListenerInfo info : headTrackingListeners) { - info.mExecutor.execute(() -> info.mListener - .onDesiredHeadTrackingModeChanged(Spatializer.this, mode)); - } - } + CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock, + (listener) -> listener.onDesiredHeadTrackingModeChanged( + Spatializer.this, mode)); } }