• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 com.android.server.location.listeners;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Build;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
27 import com.android.internal.util.Preconditions;
28 
29 import java.io.FileDescriptor;
30 import java.io.PrintWriter;
31 import java.util.AbstractMap;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Map.Entry;
35 import java.util.Objects;
36 import java.util.function.Function;
37 import java.util.function.Predicate;
38 
39 /**
40  * A base class to multiplex client listener registrations within system server. Every listener is
41  * represented by a registration object which stores all required state for a listener. Keys are
42  * used to uniquely identify every registration. Listener operations may be executed on
43  * registrations in order to invoke the represented listener.
44  *
45  * Registrations are divided into two categories, active registrations and inactive registrations,
46  * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
47  * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
48  * whose active state may have changed. Listeners will only be invoked for active registrations.
49  *
50  * The set of active registrations is combined into a single merged registration, which is submitted
51  * to the backing service when necessary in order to register the service. The merged registration
52  * is updated whenever the set of active registration changes.
53  *
54  * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
55  *
56  * <ul>
57  * <li>{@link #onRegister()}</li>
58  * <li>{@link ListenerRegistration#onRegister(Object)}</li>
59  * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
60  * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
61  * invoked if this registration is replacing a prior registration)</li>
62  * <li>{@link #onActive()}</li>
63  * <li>{@link ListenerRegistration#onActive()}</li>
64  * <li>{@link ListenerRegistration#onInactive()}</li>
65  * <li>{@link #onInactive()}</li>
66  * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}</li>
67  * <li>{@link ListenerRegistration#onUnregister()}</li>
68  * <li>{@link #onUnregister()}</li>
69  * </ul>
70  *
71  * Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
72  * other operation or callback. Removal is allowed re-entrantly, however only via
73  * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
74  * ensures re-entrant removal does not accidentally remove the incorrect registration.
75  *
76  * @param <TKey>                key type
77  * @param <TListener>           listener type
78  * @param <TRegistration>       registration type
79  * @param <TMergedRegistration> merged registration type
80  */
81 public abstract class ListenerMultiplexer<TKey, TListener,
82         TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> {
83 
84     @GuardedBy("mRegistrations")
85     private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
86 
87     @GuardedBy("mRegistrations")
88     private final UpdateServiceBuffer mUpdateServiceBuffer = new UpdateServiceBuffer();
89 
90     @GuardedBy("mRegistrations")
91     private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
92 
93     @GuardedBy("mRegistrations")
94     private int mActiveRegistrationsCount = 0;
95 
96     @GuardedBy("mRegistrations")
97     private boolean mServiceRegistered = false;
98 
99     @GuardedBy("mRegistrations")
100     @Nullable private TMergedRegistration mMerged;
101 
102     /**
103      * Should be implemented to return a unique identifying tag that may be used for logging, etc...
104      */
getTag()105     public abstract @NonNull String getTag();
106 
107     /**
108      * Should be implemented to register with the backing service with the given merged
109      * registration, and should return true if a matching call to {@link #unregisterWithService()}
110      * is required to unregister (ie, if registration succeeds). The set of registrations passed in
111      * is the same set passed into {@link #mergeRegistrations(Collection)} to generate the merged
112      * registration.
113      *
114      * <p class="note">It may seem redundant to pass in the set of active registrations when they
115      * have already been used to generate the merged request, and indeed, for many implementations
116      * this parameter can likely simply be ignored. However, some implementations may require access
117      * to the set of registrations used to generate the merged requestion for further logic even
118      * after the merged registration has been generated.
119      *
120      * @see #mergeRegistrations(Collection)
121      * @see #reregisterWithService(Object, Object, Collection)
122      */
registerWithService(TMergedRegistration merged, @NonNull Collection<TRegistration> registrations)123     protected abstract boolean registerWithService(TMergedRegistration merged,
124             @NonNull Collection<TRegistration> registrations);
125 
126     /**
127      * Invoked when the service has already been registered with some merged registration, and is
128      * now being registered with a different merged registration. The default implementation simply
129      * invokes {@link #registerWithService(Object, Collection)}.
130      *
131      * @see #registerWithService(Object, Collection)
132      */
reregisterWithService(TMergedRegistration oldMerged, TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations)133     protected boolean reregisterWithService(TMergedRegistration oldMerged,
134             TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
135         return registerWithService(newMerged, registrations);
136     }
137 
138     /**
139      * Should be implemented to unregister from the backing service.
140      */
unregisterWithService()141     protected abstract void unregisterWithService();
142 
143     /**
144      * Defines whether a registration is currently active or not. Only active registrations will be
145      * forwarded to {@link #registerWithService(Object, Collection)}, and listener invocations will
146      * only be delivered to active requests. If a registration's active state changes,
147      * {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
148      * any registrations that may have changed their active state.
149      */
isActive(@onNull TRegistration registration)150     protected abstract boolean isActive(@NonNull TRegistration registration);
151 
152     /**
153      * Called in order to generate a merged registration from the given set of active registrations.
154      * The list of registrations will never be empty. If the resulting merged registration is equal
155      * to the currently registered merged registration, nothing further will happen. If the merged
156      * registration differs, {@link #registerWithService(Object, Collection)} or
157      * {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
158      * merged registration so that the backing service can be updated.
159      */
mergeRegistrations( @onNull Collection<TRegistration> registrations)160     protected abstract @Nullable TMergedRegistration mergeRegistrations(
161             @NonNull Collection<TRegistration> registrations);
162 
163     /**
164      * Invoked when the multiplexer goes from having no registrations to having some registrations.
165      * This is a convenient entry point for registering listeners, etc, which only need to be
166      * present while there are any registrations. Invoked while holding the multiplexer's internal
167      * lock.
168      */
onRegister()169     protected void onRegister() {}
170 
171     /**
172      * Invoked when the multiplexer goes from having some registrations to having no registrations.
173      * This is a convenient entry point for unregistering listeners, etc, which only need to be
174      * present while there are any registrations. Invoked while holding the multiplexer's internal
175      * lock.
176      */
onUnregister()177     protected void onUnregister() {}
178 
179     /**
180      * Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
181      */
onRegistrationAdded(@onNull TKey key, @NonNull TRegistration registration)182     protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
183 
184     /**
185      * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
186      * registration is replacing an old registration. The old registration will have already been
187      * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
188      * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
189      */
onRegistrationReplaced(@onNull TKey key, @NonNull TRegistration oldRegistration, @NonNull TRegistration newRegistration)190     protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
191             @NonNull TRegistration newRegistration) {
192         onRegistrationAdded(key, newRegistration);
193     }
194 
195     /**
196      * Invoked when a registration is removed. Invoked while holding the multiplexer's internal
197      * lock.
198      */
onRegistrationRemoved(@onNull TKey key, @NonNull TRegistration registration)199     protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
200 
201     /**
202      * Invoked when the multiplexer goes from having no active registrations to having some active
203      * registrations. This is a convenient entry point for registering listeners, etc, which only
204      * need to be present while there are active registrations. Invoked while holding the
205      * multiplexer's internal lock.
206      */
onActive()207     protected void onActive() {}
208 
209     /**
210      * Invoked when the multiplexer goes from having some active registrations to having no active
211      * registrations. This is a convenient entry point for unregistering listeners, etc, which only
212      * need to be present while there are active registrations. Invoked while holding the
213      * multiplexer's internal lock.
214      */
onInactive()215     protected void onInactive() {}
216 
217     /**
218      * Puts a new registration with the given key, replacing any previous registration under the
219      * same key. This method cannot be called to put a registration re-entrantly.
220      */
putRegistration(@onNull TKey key, @NonNull TRegistration registration)221     protected final void putRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
222         replaceRegistration(key, key, registration);
223     }
224 
225     /**
226      * Atomically removes the registration with the old key and adds a new registration with the
227      * given key. If there was a registration for the old key,
228      * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
229      * invoked for the new registration and key instead of
230      * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
231      * the same key. The old key may be the same value as the new key, in which case this function
232      * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
233      * be called to add a registration re-entrantly.
234      */
replaceRegistration(@onNull TKey oldKey, @NonNull TKey key, @NonNull TRegistration registration)235     protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
236             @NonNull TRegistration registration) {
237         Objects.requireNonNull(oldKey);
238         Objects.requireNonNull(key);
239         Objects.requireNonNull(registration);
240 
241         synchronized (mRegistrations) {
242             // adding listeners reentrantly is not supported
243             Preconditions.checkState(!mReentrancyGuard.isReentrant());
244 
245             // new key may only have a prior registration if the oldKey is the same as the key
246             Preconditions.checkArgument(oldKey == key || !mRegistrations.containsKey(key));
247 
248             // since adding a registration can invoke a variety of callbacks, we need to ensure
249             // those callbacks themselves do not re-enter, as this could lead to out-of-order
250             // callbacks. further, we buffer service updates since adding a registration may
251             // involve removing a prior registration. note that try-with-resources ordering is
252             // meaningful here as well. we want to close the reentrancy guard first, as this may
253             // generate additional service updates, then close the update service buffer.
254             try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
255                  ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
256 
257                 boolean wasEmpty = mRegistrations.isEmpty();
258 
259                 TRegistration oldRegistration = null;
260                 int index = mRegistrations.indexOfKey(oldKey);
261                 if (index >= 0) {
262                     oldRegistration = removeRegistration(index, oldKey != key);
263                 }
264                 if (oldKey == key && index >= 0) {
265                     mRegistrations.setValueAt(index, registration);
266                 } else {
267                     mRegistrations.put(key, registration);
268                 }
269 
270                 if (wasEmpty) {
271                     onRegister();
272                 }
273                 registration.onRegister(key);
274                 if (oldRegistration == null) {
275                     onRegistrationAdded(key, registration);
276                 } else {
277                     onRegistrationReplaced(key, oldRegistration, registration);
278                 }
279                 onRegistrationActiveChanged(registration);
280             }
281         }
282     }
283 
284     /**
285      * Removes the registration with the given key. This method cannot be called to remove a
286      * registration re-entrantly.
287      */
removeRegistration(@onNull Object key)288     protected final void removeRegistration(@NonNull Object key) {
289         synchronized (mRegistrations) {
290             // this method does not support removing listeners reentrantly
291             Preconditions.checkState(!mReentrancyGuard.isReentrant());
292 
293             int index = mRegistrations.indexOfKey(key);
294             if (index < 0) {
295                 return;
296             }
297 
298             removeRegistration(index, true);
299         }
300     }
301 
302     /**
303      * Removes all registrations with keys that satisfy the given predicate. This method cannot be
304      * called to remove a registration re-entrantly.
305      */
removeRegistrationIf(@onNull Predicate<TKey> predicate)306     protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
307         synchronized (mRegistrations) {
308             // this method does not support removing listeners reentrantly
309             Preconditions.checkState(!mReentrancyGuard.isReentrant());
310 
311             // since removing a registration can invoke a variety of callbacks, we need to ensure
312             // those callbacks themselves do not re-enter, as this could lead to out-of-order
313             // callbacks. further, we buffer service updates since chains of removeLater()
314             // invocations could result in multiple service updates. note that try-with-resources
315             // ordering is meaningful here as well. we want to close the reentrancy guard first, as
316             // this may generate additional service updates, then close the update service buffer.
317             try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
318                  ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
319 
320                 final int size = mRegistrations.size();
321                 for (int i = 0; i < size; i++) {
322                     TKey key = mRegistrations.keyAt(i);
323                     if (predicate.test(key)) {
324                         removeRegistration(key, mRegistrations.valueAt(i));
325                     }
326                 }
327             }
328         }
329     }
330 
331     /**
332      * Removes the given registration with the given key. If the given key has a different
333      * registration at the time this method is called, nothing happens. This method allows for
334      * re-entrancy, and may be called to remove a registration re-entrantly.
335      */
removeRegistration(@onNull Object key, @NonNull ListenerRegistration<?> registration)336     protected final void removeRegistration(@NonNull Object key,
337             @NonNull ListenerRegistration<?> registration) {
338         synchronized (mRegistrations) {
339             int index = mRegistrations.indexOfKey(key);
340             if (index < 0) {
341                 return;
342             }
343 
344             TRegistration typedRegistration = mRegistrations.valueAt(index);
345             if (typedRegistration != registration) {
346                 return;
347             }
348 
349             if (mReentrancyGuard.isReentrant()) {
350                 unregister(typedRegistration);
351                 mReentrancyGuard.markForRemoval(key, typedRegistration);
352             } else {
353                 removeRegistration(index, true);
354             }
355         }
356     }
357 
358     @GuardedBy("mRegistrations")
removeRegistration(int index, boolean removeEntry)359     private TRegistration removeRegistration(int index, boolean removeEntry) {
360         if (Build.IS_DEBUGGABLE) {
361             Preconditions.checkState(Thread.holdsLock(mRegistrations));
362         }
363 
364         TKey key = mRegistrations.keyAt(index);
365         TRegistration registration = mRegistrations.valueAt(index);
366 
367         // since removing a registration can invoke a variety of callbacks, we need to ensure those
368         // callbacks themselves do not re-enter, as this could lead to out-of-order callbacks.
369         // further, we buffer service updates since chains of removeLater() invocations could result
370         // in multiple service updates. note that try-with-resources ordering is meaningful here as
371         // well. we want to close the reentrancy guard first, as this may generate additional
372         // service updates, then close the update service buffer.
373         try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
374              ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
375 
376             unregister(registration);
377             onRegistrationRemoved(key, registration);
378             registration.onUnregister();
379             if (removeEntry) {
380                 mRegistrations.removeAt(index);
381                 if (mRegistrations.isEmpty()) {
382                     onUnregister();
383                 }
384             }
385         }
386 
387         return registration;
388     }
389 
390     /**
391      * Forces a re-evalution of the merged request for all active registrations and updates service
392      * registration accordingly.
393      */
updateService()394     protected final void updateService() {
395         synchronized (mRegistrations) {
396             if (mUpdateServiceBuffer.isBuffered()) {
397                 mUpdateServiceBuffer.markUpdateServiceRequired();
398                 return;
399             }
400 
401             ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
402             final int size = mRegistrations.size();
403             for (int i = 0; i < size; i++) {
404                 TRegistration registration = mRegistrations.valueAt(i);
405                 if (registration.isActive()) {
406                     actives.add(registration);
407                 }
408             }
409 
410             if (actives.isEmpty()) {
411                 if (mServiceRegistered) {
412                     mMerged = null;
413                     mServiceRegistered = false;
414                     unregisterWithService();
415                 }
416                 return;
417             }
418 
419             TMergedRegistration merged = mergeRegistrations(actives);
420             if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
421                 if (mServiceRegistered) {
422                     mServiceRegistered = reregisterWithService(mMerged, merged, actives);
423                 } else {
424                     mServiceRegistered = registerWithService(merged, actives);
425                 }
426                 mMerged = mServiceRegistered ? merged : null;
427             }
428         }
429     }
430 
431     /**
432      * If the service is currently registered, unregisters it and then calls
433      * {@link #updateService()} so that {@link #registerWithService(Object, Collection)} will be
434      * re-invoked. This is useful, for instance, if the backing service has crashed or otherwise
435      * lost state, and needs to be re-initialized. Because this unregisters first, this is safe to
436      * use even if there is a possibility the backing server has not crashed, or has already been
437      * reinitialized.
438      */
resetService()439     protected final void resetService() {
440         synchronized (mRegistrations) {
441             if (mServiceRegistered) {
442                 mMerged = null;
443                 mServiceRegistered = false;
444                 unregisterWithService();
445                 updateService();
446             }
447         }
448     }
449 
450     /**
451      * Begins buffering calls to {@link #updateService()} until {@link UpdateServiceLock#close()}
452      * is called. This is useful to prevent extra work when combining multiple calls (for example,
453      * buffering {@code updateService()} until after multiple adds/removes/updates occur.
454      */
newUpdateServiceLock()455     public UpdateServiceLock newUpdateServiceLock() {
456         return new UpdateServiceLock(mUpdateServiceBuffer.acquire());
457     }
458 
459     /**
460      * Evaluates the predicate on all registrations. The predicate should return true if the active
461      * state of the registration may have changed as a result. If the active state of any
462      * registration has changed, {@link #updateService()} will automatically be invoked to handle
463      * the resulting changes.
464      */
updateRegistrations(@onNull Predicate<TRegistration> predicate)465     protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
466         synchronized (mRegistrations) {
467             // since updating a registration can invoke a variety of callbacks, we need to ensure
468             // those callbacks themselves do not re-enter, as this could lead to out-of-order
469             // callbacks. note that try-with-resources ordering is meaningful here as well. we want
470             // to close the reentrancy guard first, as this may generate additional service updates,
471             // then close the update service buffer.
472             try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
473                  ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
474 
475                 final int size = mRegistrations.size();
476                 for (int i = 0; i < size; i++) {
477                     TRegistration registration = mRegistrations.valueAt(i);
478                     if (predicate.test(registration)) {
479                         onRegistrationActiveChanged(registration);
480                     }
481                 }
482             }
483         }
484     }
485 
486     /**
487      * Evaluates the predicate on a registration with the given key. The predicate should return
488      * true if the active state of the registration may have changed as a result. If the active
489      * state of the registration has changed, {@link #updateService()} will automatically be invoked
490      * to handle the resulting changes. Returns true if there is a registration with the given key
491      * (and thus the predicate was invoked), and false otherwise.
492      */
updateRegistration(@onNull Object key, @NonNull Predicate<TRegistration> predicate)493     protected final boolean updateRegistration(@NonNull Object key,
494             @NonNull Predicate<TRegistration> predicate) {
495         synchronized (mRegistrations) {
496             // since updating a registration can invoke a variety of callbacks, we need to ensure
497             // those callbacks themselves do not re-enter, as this could lead to out-of-order
498             // callbacks. note that try-with-resources ordering is meaningful here as well. we want
499             // to close the reentrancy guard first, as this may generate additional service updates,
500             // then close the update service buffer.
501             try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
502                  ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
503 
504                 int index = mRegistrations.indexOfKey(key);
505                 if (index < 0) {
506                     return false;
507                 }
508 
509                 TRegistration registration = mRegistrations.valueAt(index);
510                 if (predicate.test(registration)) {
511                     onRegistrationActiveChanged(registration);
512                 }
513                 return true;
514             }
515         }
516     }
517 
518     @GuardedBy("mRegistrations")
onRegistrationActiveChanged(TRegistration registration)519     private void onRegistrationActiveChanged(TRegistration registration) {
520         if (Build.IS_DEBUGGABLE) {
521             Preconditions.checkState(Thread.holdsLock(mRegistrations));
522         }
523 
524         boolean active = registration.isRegistered() && isActive(registration);
525         boolean changed = registration.setActive(active);
526         if (changed) {
527             if (active) {
528                 if (++mActiveRegistrationsCount == 1) {
529                     onActive();
530                 }
531                 registration.onActive();
532             } else {
533                 registration.onInactive();
534                 if (--mActiveRegistrationsCount == 0) {
535                     onInactive();
536                 }
537             }
538 
539             updateService();
540         }
541     }
542 
543     /**
544      * Executes the given function for all active registrations. If the function returns a non-null
545      * operation, that operation will be invoked with the associated listener. The function may not
546      * change the active state of the registration.
547      */
deliverToListeners( @onNull Function<TRegistration, ListenerOperation<TListener>> function)548     protected final void deliverToListeners(
549             @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
550         synchronized (mRegistrations) {
551             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
552                 final int size = mRegistrations.size();
553                 for (int i = 0; i < size; i++) {
554                     TRegistration registration = mRegistrations.valueAt(i);
555                     if (registration.isActive()) {
556                         ListenerOperation<TListener> operation = function.apply(registration);
557                         if (operation != null) {
558                             registration.executeOperation(operation);
559                         }
560                     }
561                 }
562             }
563         }
564     }
565 
566     /**
567      * Executes the given operation for all active listeners. This is a convenience function
568      * equivalent to:
569      * <pre>
570      * deliverToListeners(registration -> operation);
571      * </pre>
572      */
deliverToListeners(@onNull ListenerOperation<TListener> operation)573     protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
574         synchronized (mRegistrations) {
575             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
576                 final int size = mRegistrations.size();
577                 for (int i = 0; i < size; i++) {
578                     TRegistration registration = mRegistrations.valueAt(i);
579                     if (registration.isActive()) {
580                         registration.executeOperation(operation);
581                     }
582                 }
583             }
584         }
585     }
586 
unregister(TRegistration registration)587     private void unregister(TRegistration registration) {
588         registration.unregisterInternal();
589         onRegistrationActiveChanged(registration);
590     }
591 
592     /**
593      * Dumps debug information.
594      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)595     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
596         synchronized (mRegistrations) {
597             pw.print("service: ");
598             pw.print(getServiceState());
599             pw.println();
600 
601             if (!mRegistrations.isEmpty()) {
602                 pw.println("listeners:");
603 
604                 final int size = mRegistrations.size();
605                 for (int i = 0; i < size; i++) {
606                     TRegistration registration = mRegistrations.valueAt(i);
607                     pw.print("  ");
608                     pw.print(registration);
609                     if (!registration.isActive()) {
610                         pw.println(" (inactive)");
611                     } else {
612                         pw.println();
613                     }
614                 }
615             }
616         }
617     }
618 
619     /**
620      * May be overridden to provide additional details on service state when dumping the manager
621      * state. Invoked while holding the multiplexer's internal lock.
622      */
getServiceState()623     protected String getServiceState() {
624         if (mServiceRegistered) {
625             if (mMerged != null) {
626                 return mMerged.toString();
627             } else {
628                 return "registered";
629             }
630         } else {
631             return "unregistered";
632         }
633     }
634 
635     /**
636      * A reference counted helper class that guards against re-entrancy, and also helps implement
637      * registration removal during reentrancy. When this class is {@link #acquire()}d, it increments
638      * the reference count. To check whether re-entrancy is occurring, clients may use
639      * {@link #isReentrant()}, and modify their behavior (such as by failing the call, or calling
640      * {@link #markForRemoval(Object, ListenerRegistration)}). When this class is {@link #close()}d,
641      * any key/registration pairs that were marked for removal prior to the close operation will
642      * then be removed - which is safe since the operation will no longer be re-entrant.
643      */
644     private final class ReentrancyGuard implements AutoCloseable {
645 
646         @GuardedBy("mRegistrations")
647         private int mGuardCount;
648         @GuardedBy("mRegistrations")
649         private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
650 
ReentrancyGuard()651         ReentrancyGuard() {
652             mGuardCount = 0;
653             mScheduledRemovals = null;
654         }
655 
656         @GuardedBy("mRegistrations")
isReentrant()657         boolean isReentrant() {
658             if (Build.IS_DEBUGGABLE) {
659                 Preconditions.checkState(Thread.holdsLock(mRegistrations));
660             }
661             return mGuardCount != 0;
662         }
663 
664         @GuardedBy("mRegistrations")
markForRemoval(Object key, ListenerRegistration<?> registration)665         void markForRemoval(Object key, ListenerRegistration<?> registration) {
666             if (Build.IS_DEBUGGABLE) {
667                 Preconditions.checkState(Thread.holdsLock(mRegistrations));
668             }
669             Preconditions.checkState(isReentrant());
670 
671             if (mScheduledRemovals == null) {
672                 mScheduledRemovals = new ArraySet<>(mRegistrations.size());
673             }
674             mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
675         }
676 
acquire()677         ReentrancyGuard acquire() {
678             ++mGuardCount;
679             return this;
680         }
681 
682         @Override
close()683         public void close() {
684             ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
685 
686             Preconditions.checkState(mGuardCount > 0);
687             if (--mGuardCount == 0) {
688                 scheduledRemovals = mScheduledRemovals;
689                 mScheduledRemovals = null;
690             }
691 
692             if (scheduledRemovals == null) {
693                 return;
694             }
695 
696             try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
697                 final int size = scheduledRemovals.size();
698                 for (int i = 0; i < size; i++) {
699                     Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
700                     removeRegistration(entry.getKey(), entry.getValue());
701                 }
702             }
703         }
704     }
705 
706     /**
707      * A reference counted helper class that buffers class to {@link #updateService()}. Since
708      * {@link #updateService()} iterates through every registration and performs request merging
709      * work, it can often be the most expensive part of any update to the multiplexer. This means
710      * that if multiple calls to updateService() can be buffered, work will be saved. This class
711      * allows clients to begin buffering calls after {@link #acquire()}ing this class, and when
712      * {@link #close()} is called, any buffered calls to {@link #updateService()} will be combined
713      * into a single final call. Clients should acquire this class when they are doing work that
714      * could potentially result in multiple calls to updateService(), and close when they are done
715      * with that work.
716      */
717     private final class UpdateServiceBuffer implements AutoCloseable {
718 
719         // requires internal locking because close() may be exposed externally and could be called
720         // from any thread
721 
722         @GuardedBy("this")
723         private int mBufferCount;
724         @GuardedBy("this")
725         private boolean mUpdateServiceRequired;
726 
UpdateServiceBuffer()727         UpdateServiceBuffer() {
728             mBufferCount = 0;
729             mUpdateServiceRequired = false;
730         }
731 
isBuffered()732         synchronized boolean isBuffered() {
733             return mBufferCount != 0;
734         }
735 
markUpdateServiceRequired()736         synchronized void markUpdateServiceRequired() {
737             Preconditions.checkState(isBuffered());
738             mUpdateServiceRequired = true;
739         }
740 
acquire()741         synchronized UpdateServiceBuffer acquire() {
742             ++mBufferCount;
743             return this;
744         }
745 
746         @Override
close()747         public void close() {
748             boolean updateServiceRequired = false;
749             synchronized (this) {
750                 Preconditions.checkState(mBufferCount > 0);
751                 if (--mBufferCount == 0) {
752                     updateServiceRequired = mUpdateServiceRequired;
753                     mUpdateServiceRequired = false;
754                 }
755             }
756 
757             if (updateServiceRequired) {
758                 updateService();
759             }
760         }
761     }
762 
763     /**
764      * Acquiring this lock will buffer all calls to {@link #updateService()} until the lock is
765      * {@link #close()}ed. This can be used to save work by acquiring the lock before multiple calls
766      * to updateService() are expected, and closing the lock after.
767      */
768     public final class UpdateServiceLock implements AutoCloseable {
769 
770         private @Nullable UpdateServiceBuffer mUpdateServiceBuffer;
771 
UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer)772         UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer) {
773             mUpdateServiceBuffer = updateServiceBuffer;
774         }
775 
776         @Override
close()777         public void close() {
778             if (mUpdateServiceBuffer != null) {
779                 UpdateServiceBuffer buffer = mUpdateServiceBuffer;
780                 mUpdateServiceBuffer = null;
781                 buffer.close();
782             }
783         }
784     }
785 }
786