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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
224
media/java/android/media/CallbackUtil.java
Normal file
224
media/java/android/media/CallbackUtil.java
Normal 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user