• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SuppressLint;
22 import android.media.permission.ClearCallingIdentityContext;
23 import android.media.permission.SafeCloseable;
24 import android.util.Log;
25 import android.util.Pair;
26 
27 import com.android.internal.annotations.GuardedBy;
28 
29 import java.util.ArrayList;
30 import java.util.Objects;
31 import java.util.concurrent.Executor;
32 
33 /**
34  * @hide
35  * A utility class to implement callback listeners and their management.
36  * This is meant to be used for lazily-initialized listener lists and stubs for event reception,
37  * typically received from server (e.g. AudioService).
38  */
39 
40 /*package*/ class CallbackUtil {
41 
42     private static final String TAG = "CallbackUtil";
43 
44     /**
45      * Container class to store a listener and associated Executor
46      * @param <T> the type of the listener
47      */
48     static class ListenerInfo<T> {
49         final @NonNull T mListener;
50         final @NonNull Executor mExecutor;
51 
ListenerInfo(@onNull T listener, @NonNull Executor exe)52         ListenerInfo(@NonNull T listener, @NonNull Executor exe) {
53             mListener = listener;
54             mExecutor = exe;
55         }
56     }
57 
58     /**
59      * Finds the listener information (listener + Executor) in a given list of listeners
60      * @param listener the listener to find
61      * @param listeners the list of listener informations, can be null if not instantiated yet
62      * @param <T> the type of the listeners
63      * @return null if the listener is not in the given list of listener informations
64      */
getListenerInfo( @onNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners)65     static <T> @Nullable ListenerInfo<T> getListenerInfo(
66             @NonNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners) {
67         if (listeners == null) {
68             return null;
69         }
70         for (ListenerInfo<T> info : listeners) {
71             if (info.mListener == listener) {
72                 return info;
73             }
74         }
75         return null;
76     }
77 
78     /**
79      * Returns true if the given listener is present in the list of listener informations
80      * @param listener the listener to find
81      * @param listeners the list of listener informations, can be null if not instantiated yet
82      * @param <T> the type of the listeners
83      * @return true if the listener is in the list
84      */
hasListener(@onNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners)85     static <T> boolean hasListener(@NonNull T listener,
86             @Nullable ArrayList<ListenerInfo<T>> listeners) {
87         return getListenerInfo(listener, listeners) != null;
88     }
89 
90     /**
91      * Removes the given listener from the list of listener informations
92      * @param listener the listener to remove
93      * @param listeners the list of listener informations, can be null if not instantiated yet
94      * @param <T> the type of the listeners
95      * @return true if the listener was found and removed from the list, false otherwise
96      */
removeListener(@onNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners)97     static <T> boolean removeListener(@NonNull T listener,
98             @Nullable ArrayList<ListenerInfo<T>> listeners) {
99         final ListenerInfo<T> infoToRemove = getListenerInfo(listener, listeners);
100         if (infoToRemove != null) {
101             listeners.remove(infoToRemove);
102             return true;
103         }
104         return false;
105     }
106 
107     /**
108      * Adds a listener and associated Executor in the list of listeners.
109      * This method handles the lazy initialization of both the list of listeners and the stub
110      * used to receive the events that will be forwarded to the listener, see the returned pair
111      * for the updated references.
112      * @param methodName the name of the method calling this, for inclusion in the
113      *                   string in case of IllegalArgumentException
114      * @param executor the Executor for the listener
115      * @param listener the listener to add
116      * @param listeners the list of listener informations, can be null if not instantiated yet
117      * @param dispatchStub the stub that receives the events to be forwarded to the listeners,
118      *                    can be null if not instantiated yet
119      * @param newStub the function to create a new stub if needed
120      * @param registerStub the function for the stub registration if needed
121      * @param <T> the type of the listener interface
122      * @param <S> the type of the event receiver stub
123      * @return a pair of the listener list and the event receiver stub which may have been
124      *         initialized if needed (e.g. on the first ever addition of a listener)
125      */
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)126     static <T, S> Pair<ArrayList<ListenerInfo<T>>, S> addListener(String methodName,
127             @NonNull Executor executor,
128             @NonNull T listener,
129             @Nullable ArrayList<ListenerInfo<T>> listeners,
130             @Nullable S dispatchStub,
131             @NonNull java.util.function.Supplier<S> newStub,
132             @NonNull java.util.function.Consumer<S> registerStub) {
133         Objects.requireNonNull(executor);
134         Objects.requireNonNull(listener);
135 
136         if (hasListener(listener, listeners)) {
137             throw new IllegalArgumentException("attempt to call " + methodName
138                     + "on a previously registered listener");
139         }
140         // lazy initialization of the list of strategy-preferred device listener
141         if (listeners == null) {
142             listeners = new ArrayList<>();
143         }
144         if (listeners.size() == 0) {
145             // register binder for callbacks
146             if (dispatchStub == null) {
147                 try {
148                     dispatchStub = newStub.get();
149                 } catch (Exception e) {
150                     Log.e(TAG, "Exception while creating stub in " + methodName, e);
151                     return new Pair<>(null, null);
152                 }
153             }
154             registerStub.accept(dispatchStub);
155         }
156         listeners.add(new ListenerInfo<T>(listener, executor));
157         return new Pair(listeners, dispatchStub);
158     }
159 
160     /**
161      * Removes a listener from the list of listeners.
162      * This method handles the freeing of both the list of listeners and the stub
163      * used to receive the events that will be forwarded to the listener,see the returned pair
164      * for the updated references.
165      * @param methodName the name of the method calling this, for inclusion in the
166      *                   string in case of IllegalArgumentException
167      * @param listener the listener to remove
168      * @param listeners the list of listener informations, can be null if not instantiated yet
169      * @param dispatchStub the stub that receives the events to be forwarded to the listeners,
170      *                    can be null if not instantiated yet
171      * @param unregisterStub the function to unregister the stub if needed
172      * @param <T> the type of the listener interface
173      * @param <S> the type of the event receiver stub
174      * @return a pair of the listener list and the event receiver stub which may have been
175      *         changed if needed (e.g. on the removal of the last listener)
176      */
removeListener(String methodName, @NonNull T listener, @Nullable ArrayList<ListenerInfo<T>> listeners, @Nullable S dispatchStub, @NonNull java.util.function.Consumer<S> unregisterStub)177     static <T, S> Pair<ArrayList<ListenerInfo<T>>, S> removeListener(String methodName,
178             @NonNull T listener,
179             @Nullable ArrayList<ListenerInfo<T>> listeners,
180             @Nullable S dispatchStub,
181             @NonNull java.util.function.Consumer<S> unregisterStub) {
182         Objects.requireNonNull(listener);
183 
184         if (!removeListener(listener, listeners)) {
185             throw new IllegalArgumentException("attempt to call " + methodName
186                     + " on an unregistered listener");
187         }
188         if (listeners.size() == 0) {
189             unregisterStub.accept(dispatchStub);
190             return new Pair<>(null, null);
191         } else {
192             return new Pair<>(listeners, dispatchStub);
193         }
194     }
195 
196     interface CallbackMethod<T> {
callbackMethod(T listener)197         void callbackMethod(T listener);
198     }
199 
200     /**
201      * Exercise the callback of the listeners
202      * @param listeners the list of listeners
203      * @param listenerLock the lock guarding the list of listeners
204      * @param callback the function to call for each listener
205      * @param <T>  the type of the listener interface
206      */
callListeners( @ullable ArrayList<ListenerInfo<T>> listeners, @NonNull Object listenerLock, @NonNull CallbackMethod<T> callback)207     static <T> void callListeners(
208             @Nullable ArrayList<ListenerInfo<T>> listeners,
209             @NonNull Object listenerLock,
210             @NonNull CallbackMethod<T> callback) {
211         Objects.requireNonNull(listenerLock);
212         // make a shallow copy of listeners so callback is not executed under lock
213         final ArrayList<ListenerInfo<T>> listenersShallowCopy;
214         synchronized (listenerLock) {
215             if (listeners == null || listeners.size() == 0) {
216                 return;
217             }
218             listenersShallowCopy = (ArrayList<ListenerInfo<T>>) listeners.clone();
219         }
220         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
221             for (ListenerInfo<T> info : listenersShallowCopy) {
222                 info.mExecutor.execute(() -> callback.callbackMethod(info.mListener));
223             }
224         }
225 
226     }
227 
228     /**
229      * Interface to be implemented by stub implementation for the events received from a server
230      * to the class managing the listener API.
231      * For an example see {@link AudioManager#ModeDispatcherStub} which registers with AudioService.
232      */
233     interface DispatcherStub {
234         /**
235          * Register/unregister the stub as a listener of the events to be forwarded to the listeners
236          * managed by LazyListenerManager.
237          * @param register true for registering, false to unregister
238          */
register(boolean register)239         void register(boolean register);
240     }
241 
242     /**
243      * Class to manage a list of listeners and their callback, and the associated stub which
244      * receives the events to be forwarded to the listeners.
245      * The list of listeners and the stub and its registration are lazily initialized and registered
246      * @param <T> the listener class
247      */
248     static class LazyListenerManager<T> {
249         private final Object mListenerLock = new Object();
250 
251         @GuardedBy("mListenerLock")
252         private @Nullable ArrayList<ListenerInfo<T>> mListeners;
253 
254         @GuardedBy("mListenerLock")
255         private @Nullable DispatcherStub mDispatcherStub;
256 
LazyListenerManager()257         LazyListenerManager() {
258             // nothing to initialize as instances of dispatcher and list of listeners
259             // are lazily initialized
260         }
261 
262         /**
263          * Add a new listener / executor pair for the configured listener
264          * @param executor Executor for the callback
265          * @param listener the listener to register
266          * @param methodName the name of the method calling this utility method for easier to read
267          *          exception messages
268          * @param newStub how to build a new instance of the stub receiving the events when the
269          *          number of listeners goes from 0 to 1, not called until then.
270          */
addListener(@onNull Executor executor, @NonNull T listener, String methodName, @NonNull java.util.function.Supplier<DispatcherStub> newStub)271         void addListener(@NonNull Executor executor, @NonNull T listener, String methodName,
272                 @NonNull java.util.function.Supplier<DispatcherStub> newStub) {
273             synchronized (mListenerLock) {
274                 final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
275                         CallbackUtil.addListener(methodName,
276                                 executor, listener, mListeners, mDispatcherStub,
277                                 newStub,
278                                 stub -> stub.register(true));
279                 mListeners = res.first;
280                 mDispatcherStub = res.second;
281             }
282         }
283 
284         /**
285          * Remove a previously registered listener
286          * @param listener the listener to unregister
287          * @param methodName the name of the method calling this utility method for easier to read
288          *          exception messages
289          */
removeListener(@onNull T listener, String methodName)290         void removeListener(@NonNull T listener, String methodName) {
291             synchronized (mListenerLock) {
292                 final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
293                         CallbackUtil.removeListener(methodName,
294                                 listener, mListeners, mDispatcherStub,
295                                 stub -> stub.register(false));
296                 mListeners = res.first;
297                 mDispatcherStub = res.second;
298             }
299         }
300 
301         /**
302          * Call the registered listeners with the given callback method
303          * @param callback the listener method to invoke
304          */
305         @SuppressLint("GuardedBy") // lock applied inside callListeners method
callListeners(CallbackMethod<T> callback)306         void callListeners(CallbackMethod<T> callback) {
307             CallbackUtil.callListeners(mListeners, mListenerLock, callback);
308         }
309     }
310 }
311