1 /*
2  * Copyright (C) 2014 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.camera.util;
18 
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.EnumMap;
22 import java.util.List;
23 
24 /**
25  * Enables thread-safe multiplexing of multiple input boolean states into a
26  * single listener to be invoked upon change in the conjunction (logical AND) of
27  * all inputs.
28  */
29 public class ListenerCombiner<Input extends Enum<Input>> {
30     /**
31      * Callback for listening to changes to the conjunction of all inputs.
32      */
33     public static interface StateChangeListener {
34         /**
35          * Called whenever the conjunction of all inputs changes. Listeners MUST
36          * NOT call {@link #setInput} while still registered as a listener, as
37          * this will result in infinite recursion.
38          *
39          * @param state the conjunction of all input values.
40          */
onStateChange(boolean state)41         public void onStateChange(boolean state);
42     }
43 
44     /** Mutex for mValues and mState. */
45     private final Object mLock = new Object();
46     /** Stores the current input state. */
47     private final EnumMap<Input, Boolean> mInputs;
48     /** The current output state */
49     private boolean mOutput;
50     /**
51      * The set of listeners to notify when the output (the conjunction of all
52      * inputs) changes.
53      */
54     private final List<StateChangeListener> mListeners = Collections.synchronizedList(
55             new ArrayList<StateChangeListener>());
56 
addListener(StateChangeListener listener)57     public void addListener(StateChangeListener listener) {
58         mListeners.add(listener);
59     }
60 
removeListener(StateChangeListener listener)61     public void removeListener(StateChangeListener listener) {
62         mListeners.remove(listener);
63     }
64 
getOutput()65     public boolean getOutput() {
66         synchronized (mLock) {
67             return mOutput;
68         }
69     }
70 
71     /**
72      * Updates the state of the given input, dispatching to all output change
73      * listeners if the output changes.
74      *
75      * @param index the index of the input to change.
76      * @param newValue the new value of the input.
77      * @return The new output.
78      */
setInput(Input input, boolean newValue)79     public boolean setInput(Input input, boolean newValue) {
80         synchronized (mLock) {
81             mInputs.put(input, newValue);
82 
83             // If the new input value is the same as the existing output,
84             // then nothing will change.
85             if (newValue == mOutput) {
86                 return mOutput;
87             } else {
88                 boolean oldOutput = mOutput;
89 
90                 // Recompute the output by AND'ing all the inputs.
91                 mOutput = true;
92                 for (Boolean b : mInputs.values()) {
93                     mOutput &= b;
94                 }
95 
96                 // If the output has changed, notify the listeners.
97                 if (oldOutput != mOutput) {
98                     notifyListeners();
99                 }
100 
101                 return mOutput;
102             }
103         }
104     }
105 
ListenerCombiner(Class<Input> clazz, StateChangeListener listener)106     public ListenerCombiner(Class<Input> clazz, StateChangeListener listener) {
107         this(clazz);
108         addListener(listener);
109     }
110 
ListenerCombiner(Class<Input> clazz)111     public ListenerCombiner(Class<Input> clazz) {
112         mInputs = new EnumMap<Input, Boolean>(clazz);
113 
114         for (Input i : clazz.getEnumConstants()) {
115             mInputs.put(i, false);
116         }
117 
118         mOutput = false;
119     }
120 
121     /**
122      * Notifies all listeners of the current state, regardless of whether or not
123      * it has actually changed.
124      */
notifyListeners()125     public void notifyListeners() {
126         synchronized (mLock) {
127             for (StateChangeListener listener : mListeners) {
128                 listener.onStateChange(mOutput);
129             }
130         }
131     }
132 }
133