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