1 /*
2  * Copyright (C) 2015 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.one.v2.sharedimagereader.ringbuffer;
18 
19 import com.android.camera.async.ConcurrentState;
20 import com.android.camera.async.Lifetime;
21 import com.android.camera.async.Observable;
22 import com.android.camera.async.SafeCloseable;
23 
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.concurrent.Executor;
27 import java.util.concurrent.atomic.AtomicInteger;
28 
29 import javax.annotation.Nonnull;
30 import javax.annotation.ParametersAreNonnullByDefault;
31 import javax.annotation.concurrent.ThreadSafe;
32 
33 /**
34  * Provides a listenable count of the total number of image capacity which are
35  * readily-available at any given time.
36  * <p>
37  * The total count is the sum of all inputs.
38  */
39 @ThreadSafe
40 @ParametersAreNonnullByDefault
41 final class AvailableTicketCounter implements Observable<Integer> {
42     private final List<Observable<Integer>> mInputs;
43     private final ConcurrentState<Integer> mCount;
44     private final AtomicInteger mCounterLocked;
45 
AvailableTicketCounter(List<Observable<Integer>> inputs)46     public AvailableTicketCounter(List<Observable<Integer>> inputs) {
47         mInputs = new ArrayList<>(inputs);
48         mCount = new ConcurrentState<>(0);
49         mCounterLocked = new AtomicInteger(0);
50     }
51 
52     @Nonnull
53     @Override
addCallback(final Runnable callback, final Executor executor)54     public SafeCloseable addCallback(final Runnable callback, final Executor executor) {
55         Lifetime callbackLifetime = new Lifetime();
56         for (Observable<Integer> input : mInputs) {
57             callbackLifetime.add(input.addCallback(callback, executor));
58         }
59         return callbackLifetime;
60     }
61 
compute()62     private int compute() {
63         int sum = 0;
64         for (Observable<Integer> input : mInputs) {
65             sum += input.get();
66         }
67         return sum;
68     }
69 
70     @Nonnull
71     @Override
get()72     public Integer get() {
73         int value = mCount.get();
74         if (mCounterLocked.get() == 0) {
75             value = compute();
76         }
77         return value;
78     }
79 
80     /**
81      * Locks the counter to the current value. Changes to the value, resulting
82      * from changes to the inputs, will not be reflected by {@link #get} or be
83      * propagated to callbacks until a matching call to {@link #unfreeze}.
84      */
freeze()85     public void freeze() {
86         int value = compute();
87         // Update the count with the current, valid value before freezing it, if
88         // it was not already frozen.
89         mCounterLocked.incrementAndGet();
90         mCount.update(value);
91     }
92 
93     /**
94      * @see {@link #freeze}
95      */
unfreeze()96     public void unfreeze() {
97         // If this invocation released the last logical "lock" on the counter,
98         // then update with the latest value.
99         // Note that the value used *must* be that recomputed before
100         // decrementing the lock counter to guarantee that we only update
101         // listeners with a valid value.
102         int newValue = compute();
103         int numLocksHeld = mCounterLocked.decrementAndGet();
104         if (numLocksHeld == 0) {
105             mCount.update(newValue);
106         }
107     }
108 }
109