| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.camera.core; |
| |
| import android.util.SparseArray; |
| |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.NonNull; |
| import androidx.concurrent.futures.CallbackToFutureAdapter; |
| |
| import com.google.common.util.concurrent.ListenableFuture; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * A {@link ImageProxyBundle} with a predefined set of captured ids. The {@link ListenableFuture} |
| * for the capture id becomes valid when the corresponding {@link ImageProxy} has been set. |
| */ |
| final class SettableImageProxyBundle implements ImageProxyBundle { |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| final Object mLock = new Object(); |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @GuardedBy("mLock") |
| final SparseArray<CallbackToFutureAdapter.Completer<ImageProxy>> mCompleters = |
| new SparseArray<>(); |
| /** Map of id to {@link ImageProxy} Future. */ |
| @GuardedBy("mLock") |
| private final SparseArray<ListenableFuture<ImageProxy>> mFutureResults = new SparseArray<>(); |
| |
| @GuardedBy("mLock") |
| private final List<ImageProxy> mOwnedImageProxies = new ArrayList<>(); |
| |
| private final List<Integer> mCaptureIdList; |
| // Whether or not the bundle has been closed or not |
| @GuardedBy("mLock") |
| private boolean mClosed = false; |
| |
| /** |
| * Create a {@link ImageProxyBundle} for captures with the given ids. |
| * |
| * @param captureIds The set of captureIds contained by the ImageProxyBundle |
| */ |
| SettableImageProxyBundle(List<Integer> captureIds) { |
| mCaptureIdList = captureIds; |
| setup(); |
| } |
| |
| @Override |
| public ListenableFuture<ImageProxy> getImageProxy(int captureId) { |
| synchronized (mLock) { |
| if (mClosed) { |
| throw new IllegalStateException("ImageProxyBundle already closed."); |
| } |
| |
| // Returns the future that has been set if it exists |
| ListenableFuture<ImageProxy> result = mFutureResults.get(captureId); |
| if (result == null) { |
| throw new IllegalArgumentException( |
| "ImageProxyBundle does not contain this id: " + captureId); |
| } |
| |
| return result; |
| } |
| } |
| |
| @Override |
| public List<Integer> getCaptureIds() { |
| return Collections.unmodifiableList(mCaptureIdList); |
| } |
| |
| /** |
| * Add an {@link ImageProxy} to synchronize. |
| */ |
| void addImageProxy(ImageProxy imageProxy) { |
| synchronized (mLock) { |
| if (mClosed) { |
| return; |
| } |
| |
| Integer captureId = (Integer) imageProxy.getImageInfo().getTag(); |
| if (captureId == null) { |
| throw new IllegalArgumentException("CaptureId is null."); |
| } |
| |
| // If the CaptureId is associated with this SettableImageProxyBundle, set the |
| // corresponding Future. Otherwise, throws exception. |
| CallbackToFutureAdapter.Completer<ImageProxy> completer = mCompleters.get(captureId); |
| if (completer != null) { |
| completer.set(imageProxy); |
| mOwnedImageProxies.add(imageProxy); |
| } else { |
| throw new IllegalArgumentException( |
| "ImageProxyBundle does not contain this id: " + captureId); |
| } |
| } |
| } |
| |
| /** |
| * Flush all {@link ImageProxy} that have been added. |
| */ |
| void close() { |
| synchronized (mLock) { |
| if (mClosed) { |
| return; |
| } |
| for (ImageProxy imageProxy : mOwnedImageProxies) { |
| imageProxy.close(); |
| } |
| mOwnedImageProxies.clear(); |
| mFutureResults.clear(); |
| mCompleters.clear(); |
| mClosed = true; |
| } |
| } |
| |
| /** |
| * Clear all {@link ImageProxy} that have been added and recreate the entries from the bundle. |
| */ |
| void reset() { |
| synchronized (mLock) { |
| if (mClosed) { |
| return; |
| } |
| for (ImageProxy imageProxy : mOwnedImageProxies) { |
| imageProxy.close(); |
| } |
| mOwnedImageProxies.clear(); |
| mFutureResults.clear(); |
| mCompleters.clear(); |
| setup(); |
| } |
| } |
| |
| private void setup() { |
| synchronized (mLock) { |
| for (final int captureId : mCaptureIdList) { |
| ListenableFuture<ImageProxy> futureResult = CallbackToFutureAdapter.getFuture( |
| new CallbackToFutureAdapter.Resolver<ImageProxy>() { |
| @Override |
| public Object attachCompleter( |
| @NonNull CallbackToFutureAdapter.Completer<ImageProxy> |
| completer) { |
| synchronized (mLock) { // Not technically needed since |
| // attachCompleter is called inline, but mLock is re-entrant |
| // so there's no harm. |
| mCompleters.put(captureId, completer); |
| } |
| return "getImageProxy(id: " + captureId + ")"; |
| } |
| }); |
| mFutureResults.put(captureId, futureResult); |
| } |
| } |
| } |
| } |