AvailableTicketCounter.java revision 63204dc989dbd0eba56f65086fde0ebe29ed6bdb
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
17package com.android.camera.one.v2.sharedimagereader.ringbuffer;
18
19import com.android.camera.async.ConcurrentState;
20import com.android.camera.async.Lifetime;
21import com.android.camera.async.Observable;
22import com.android.camera.async.SafeCloseable;
23import com.google.common.collect.ImmutableList;
24
25import java.util.List;
26import java.util.concurrent.Executor;
27import java.util.concurrent.atomic.AtomicInteger;
28
29import javax.annotation.Nonnull;
30import javax.annotation.ParametersAreNonnullByDefault;
31import 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
41final class AvailableTicketCounter implements Observable<Integer> {
42    private final ImmutableList<Observable<Integer>> mInputs;
43    private final ConcurrentState<Integer> mCount;
44    private final AtomicInteger mCounterLocked;
45
46    public AvailableTicketCounter(List<Observable<Integer>> inputs) {
47        mInputs = ImmutableList.copyOf(inputs);
48        mCount = new ConcurrentState<>(0);
49        mCounterLocked = new AtomicInteger(0);
50    }
51
52    @Nonnull
53    @Override
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
62    private int recompute() {
63        int sum = 0;
64        for (Observable<Integer> input : mInputs) {
65            sum += input.get();
66        }
67        return sum;
68    }
69
70    @Nonnull
71    @Override
72    public Integer get() {
73        int value = mCount.get();
74        if (mCounterLocked.get() == 0) {
75            value = recompute();
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     */
85    public void freeze() {
86        int value = recompute();
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     */
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 = recompute();
103        int numLocksHeld = mCounterLocked.decrementAndGet();
104        if (numLocksHeld == 0) {
105            mCount.update(newValue);
106        }
107    }
108}
109