• 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 androidx.window.common;
18 
19 import androidx.annotation.GuardedBy;
20 import androidx.annotation.NonNull;
21 
22 import java.util.HashSet;
23 import java.util.LinkedHashSet;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.function.Consumer;
27 
28 /**
29  * Base class that manages listeners when listening to a piece of data that changes.  This class is
30  * thread safe for adding, removing, and notifying consumers.
31  *
32  * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}.
33  */
34 public abstract class BaseDataProducer<T> implements
35         AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
36 
37     private final Object mLock = new Object();
38     @GuardedBy("mLock")
39     private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
40     @GuardedBy("mLock")
41     private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
42 
43     /**
44      * Emits the first available data at that point in time.
45      * @param dataConsumer a {@link Consumer} that will receive one value.
46      */
getData(@onNull Consumer<T> dataConsumer)47     public abstract void getData(@NonNull Consumer<T> dataConsumer);
48 
49     /**
50      * Adds a callback to the set of callbacks listening for data. Data is delivered through
51      * {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers
52      * should ensure that callbacks are thread safe.
53      * @param callback that will receive data from the producer.
54      */
addDataChangedCallback(@onNull Consumer<T> callback)55     public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
56         synchronized (mLock) {
57             mCallbacks.add(callback);
58         }
59         Optional<T> currentData = getCurrentData();
60         currentData.ifPresent(callback);
61         onListenersChanged();
62     }
63 
64     /**
65      * Removes a callback to the set of callbacks listening for data. This method is thread safe
66      * for adding.
67      * @param callback that was registered in
68      * {@link BaseDataProducer#addDataChangedCallback(Consumer)}.
69      */
removeDataChangedCallback(@onNull Consumer<T> callback)70     public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
71         synchronized (mLock) {
72             mCallbacks.remove(callback);
73         }
74         onListenersChanged();
75     }
76 
77     /**
78      * Returns {@code true} if there are any registered callbacks {@code false} if there are no
79      * registered callbacks.
80      */
81     // TODO(b/278132889) Improve the structure of BaseDataProdcuer while avoiding known issues.
hasListeners()82     public final boolean hasListeners() {
83         synchronized (mLock) {
84             return !mCallbacks.isEmpty();
85         }
86     }
87 
onListenersChanged()88     protected void onListenersChanged() {}
89 
90     /**
91      * @return the current data if available and {@code Optional.empty()} otherwise.
92      */
93     @NonNull
getCurrentData()94     public abstract Optional<T> getCurrentData();
95 
96     /**
97      * Called to notify all registered consumers that the data provided
98      * by {@link BaseDataProducer#getData} has changed. Calls to this are thread save but callbacks
99      * need to ensure thread safety.
100      */
notifyDataChanged(T value)101     protected void notifyDataChanged(T value) {
102         synchronized (mLock) {
103             for (Consumer<T> callback : mCallbacks) {
104                 callback.accept(value);
105             }
106             removeFinishedCallbacksLocked();
107         }
108     }
109 
110     /**
111      * Removes any callbacks that notified us through {@link #onConsumerReadyToBeRemoved(Consumer)}
112      * that they are ready to be removed.
113      */
114     @GuardedBy("mLock")
removeFinishedCallbacksLocked()115     private void removeFinishedCallbacksLocked() {
116         for (Consumer<T> callback: mCallbacksToRemove) {
117             mCallbacks.remove(callback);
118         }
119         mCallbacksToRemove.clear();
120     }
121 
122     @Override
onConsumerReadyToBeRemoved(Consumer<T> callback)123     public void onConsumerReadyToBeRemoved(Consumer<T> callback) {
124         synchronized (mLock) {
125             mCallbacksToRemove.add(callback);
126         }
127     }
128 }
129