Part 2/2: class to make managing listeners even easier Add class that leverages the CallbackUtil utility methods so listener lists, locks and dispatcher stubs don't need to be members of the class using the new LazyListenerManager class. 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: Ie20c8ff2dddadc2c778c8a9ba4385f28da80e9a6
311 lines
13 KiB
Java
311 lines
13 KiB
Java
/*
|
|
* 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.annotation.SuppressLint;
|
|
import android.media.permission.ClearCallingIdentityContext;
|
|
import android.media.permission.SafeCloseable;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Interface to be implemented by stub implementation for the events received from a server
|
|
* to the class managing the listener API.
|
|
* For an example see {@link AudioManager#ModeDispatcherStub} which registers with AudioService.
|
|
*/
|
|
interface DispatcherStub {
|
|
/**
|
|
* Register/unregister the stub as a listener of the events to be forwarded to the listeners
|
|
* managed by LazyListenerManager.
|
|
* @param register true for registering, false to unregister
|
|
*/
|
|
void register(boolean register);
|
|
}
|
|
|
|
/**
|
|
* Class to manage a list of listeners and their callback, and the associated stub which
|
|
* receives the events to be forwarded to the listeners.
|
|
* The list of listeners and the stub and its registration are lazily initialized and registered
|
|
* @param <T> the listener class
|
|
*/
|
|
static class LazyListenerManager<T> {
|
|
private final Object mListenerLock = new Object();
|
|
|
|
@GuardedBy("mListenerLock")
|
|
private @Nullable ArrayList<ListenerInfo<T>> mListeners;
|
|
|
|
@GuardedBy("mListenerLock")
|
|
private @Nullable DispatcherStub mDispatcherStub;
|
|
|
|
LazyListenerManager() {
|
|
// nothing to initialize as instances of dispatcher and list of listeners
|
|
// are lazily initialized
|
|
}
|
|
|
|
/**
|
|
* Add a new listener / executor pair for the configured listener
|
|
* @param executor Executor for the callback
|
|
* @param listener the listener to register
|
|
* @param methodName the name of the method calling this utility method for easier to read
|
|
* exception messages
|
|
* @param newStub how to build a new instance of the stub receiving the events when the
|
|
* number of listeners goes from 0 to 1, not called until then.
|
|
*/
|
|
void addListener(@NonNull Executor executor, @NonNull T listener, String methodName,
|
|
@NonNull java.util.function.Supplier<DispatcherStub> newStub) {
|
|
synchronized (mListenerLock) {
|
|
final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
|
|
CallbackUtil.addListener(methodName,
|
|
executor, listener, mListeners, mDispatcherStub,
|
|
newStub,
|
|
stub -> stub.register(true));
|
|
mListeners = res.first;
|
|
mDispatcherStub = res.second;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a previously registered listener
|
|
* @param listener the listener to unregister
|
|
* @param methodName the name of the method calling this utility method for easier to read
|
|
* exception messages
|
|
*/
|
|
void removeListener(@NonNull T listener, String methodName) {
|
|
synchronized (mListenerLock) {
|
|
final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
|
|
CallbackUtil.removeListener(methodName,
|
|
listener, mListeners, mDispatcherStub,
|
|
stub -> stub.register(false));
|
|
mListeners = res.first;
|
|
mDispatcherStub = res.second;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call the registered listeners with the given callback method
|
|
* @param callback the listener method to invoke
|
|
*/
|
|
@SuppressLint("GuardedBy") // lock applied inside callListeners method
|
|
void callListeners(CallbackMethod<T> callback) {
|
|
CallbackUtil.callListeners(mListeners, mListenerLock, callback);
|
|
}
|
|
}
|
|
}
|