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