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 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 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 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 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 */ 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 */ 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