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