| /* | |
| * 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); | |
| } | |
| } | |
| } |