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