Utilities for managing event listeners from AudioService

Part 1/2: individual static helpers for managing listeners,
  more to come to encompass all functionality inside
  one class.

Implement utility classes for:
- storing information about a listener containing callback
  and Executor to use (see ListenerInfo class)
- managing listeners in a list: remove / retrieve / check
  presence of a listener in a given list (see
  removeListener / getListenerInfo / hasListener methods)
- managing listeners in a list, while managing the
  lifecycle of the list, and associated stub for receiving
  events (for instance for AudioManager to receive events
  from AudioService), including lazy initialization of
  the listener list and stub to minimize memory usage

The utility class is used to refactor the management of
the following listeners:
  Spatializer.OnSpatializerStateChangedListener
  Spatializer.OnHeadTrackingModeChangedListener
  AudioManager.OnModeChangedListener
  AudioManager.OnCommunicationDeviceChangedListener

Bug: 206040617
Test: atest AudioModeListenerTest AudioCommunicationDeviceTest \
 SpatializerTest
Change-Id: Ia37f1b19603282e0f14e6e5afc1226891e3c1d78
This commit is contained in:
Jean-Michel Trivi
2021-11-11 16:31:32 -08:00
parent 3ae8743e33
commit 91f0baa898
3 changed files with 359 additions and 431 deletions

View File

@@ -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<ModeListenerInfo> mModeListeners;
private @Nullable ArrayList<ListenerInfo<OnModeChangedListener>> 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<ModeListenerInfo> modeListeners;
synchronized (mModeListenerLock) {
if (mModeListeners == null || mModeListeners.size() == 0) {
return;
}
modeListeners = (ArrayList<ModeListenerInfo>) 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<ArrayList<ListenerInfo<OnModeChangedListener>>, 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<ArrayList<ListenerInfo<OnModeChangedListener>>, 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<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
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<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
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<CommDevListenerInfo> 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<ListenerInfo<OnCommunicationDeviceChangedListener>> 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<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();
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;
}
}
}
}

View File

@@ -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 <T> the type of the listener
*/
static class ListenerInfo<T> {
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 <T> the type of the listeners
* @return null if the listener is not in the given list of listener informations
*/
static <T> @Nullable ListenerInfo<T> getListenerInfo(
@NonNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners) {
if (listeners == null) {
return null;
}
for (ListenerInfo<T> 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 <T> the type of the listeners
* @return true if the listener is in the list
*/
static <T> boolean hasListener(@NonNull T listener,
@Nullable ArrayList<ListenerInfo<T>> 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 <T> the type of the listeners
* @return true if the listener was found and removed from the list, false otherwise
*/
static <T> boolean removeListener(@NonNull T listener,
@Nullable ArrayList<ListenerInfo<T>> listeners) {
final ListenerInfo<T> 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 <T> the type of the listener interface
* @param <S> 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 <T, S> Pair<ArrayList<ListenerInfo<T>>, S> addListener(String methodName,
@NonNull Executor executor,
@NonNull T listener,
@Nullable ArrayList<ListenerInfo<T>> listeners,
@Nullable S dispatchStub,
@NonNull java.util.function.Supplier<S> newStub,
@NonNull java.util.function.Consumer<S> 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<T>(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 <T> the type of the listener interface
* @param <S> 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 <T, S> Pair<ArrayList<ListenerInfo<T>>, S> removeListener(String methodName,
@NonNull T listener,
@Nullable ArrayList<ListenerInfo<T>> listeners,
@Nullable S dispatchStub,
@NonNull java.util.function.Consumer<S> 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<T> {
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 <T> the type of the listener interface
*/
static <T> void callListeners(
@Nullable ArrayList<ListenerInfo<T>> listeners,
@NonNull Object listenerLock,
@NonNull CallbackMethod<T> callback) {
Objects.requireNonNull(listenerLock);
// make a shallow copy of listeners so callback is not executed under lock
final ArrayList<ListenerInfo<T>> listenersShallowCopy;
synchronized (listenerLock) {
if (listeners == null || listeners.size() == 0) {
return;
}
listenersShallowCopy = (ArrayList<ListenerInfo<T>>) listeners.clone();
}
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
for (ListenerInfo<T> info : listenersShallowCopy) {
info.mExecutor.execute(() -> callback.callbackMethod(info.mListener));
}
}
}
}

View File

@@ -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<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
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<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
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<StateListenerInfo> mStateListeners;
private @Nullable ArrayList<ListenerInfo<OnSpatializerStateChangedListener>> 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<StateListenerInfo> stateListeners;
synchronized (mStateListenerLock) {
if (mStateListeners == null || mStateListeners.size() == 0) {
return;
}
stateListeners = (ArrayList<StateListenerInfo>) 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<StateListenerInfo> stateListeners;
synchronized (mStateListenerLock) {
if (mStateListeners == null || mStateListeners.size() == 0) {
return;
}
stateListeners = (ArrayList<StateListenerInfo>) 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<OnHeadTrackingModeChangedListener>(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<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
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<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
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<T> {
final @NonNull T mListener;
final @NonNull Executor mExecutor;
ListenerInfo(T listener, Executor exe) {
mListener = listener;
mExecutor = exe;
}
}
private static <T> ListenerInfo<T> getListenerInfo(
T listener, ArrayList<ListenerInfo<T>> listeners) {
if (listeners == null) {
return null;
}
for (ListenerInfo<T> info : listeners) {
if (info.mListener == listener) {
return info;
}
}
return null;
}
private static <T> boolean hasListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
return getListenerInfo(listener, listeners) != null;
}
private static <T> boolean removeListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
final ListenerInfo<T> 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<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
synchronized (mHeadTrackingListenerLock) {
if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
return;
}
headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
mHeadTrackingListeners.clone();
}
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
for (ListenerInfo<OnHeadTrackingModeChangedListener> 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<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
synchronized (mHeadTrackingListenerLock) {
if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
return;
}
headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
mHeadTrackingListeners.clone();
}
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
info.mExecutor.execute(() -> info.mListener
.onDesiredHeadTrackingModeChanged(Spatializer.this, mode));
}
}
CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
(listener) -> listener.onDesiredHeadTrackingModeChanged(
Spatializer.this, mode));
}
}