1 /*
2  * Copyright 2019 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 androidx.camera.core.impl;
18 
19 import android.view.Surface;
20 
21 import androidx.camera.core.impl.utils.futures.FutureCallback;
22 import androidx.camera.core.impl.utils.futures.Futures;
23 import androidx.concurrent.futures.CallbackToFutureAdapter;
24 import androidx.core.util.Preconditions;
25 
26 import com.google.common.util.concurrent.ListenableFuture;
27 
28 import org.jspecify.annotations.NonNull;
29 import org.jspecify.annotations.Nullable;
30 
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.concurrent.Executor;
36 import java.util.concurrent.ScheduledExecutorService;
37 import java.util.concurrent.TimeoutException;
38 
39 /**
40  * Utility functions for manipulating {@link DeferrableSurface}.
41  */
42 public final class DeferrableSurfaces {
43 
DeferrableSurfaces()44     private DeferrableSurfaces() {
45     }
46 
47     /**
48      * Returns a {@link ListenableFuture} that get the List<Surface> result form
49      * {@link DeferrableSurface} collection.
50      *
51      * @param removeNullSurfaces       If true remove all Surfaces that were not retrieved.
52      * @param timeoutMillis            The task timeout value in milliseconds.
53      * @param executor                 The executor service to run the task.
54      * @param scheduledExecutorService The executor service to schedule the timeout event.
55      */
surfaceListWithTimeout( @onNull Collection<DeferrableSurface> deferrableSurfaces, boolean removeNullSurfaces, long timeoutMillis, @NonNull Executor executor, @NonNull ScheduledExecutorService scheduledExecutorService)56     public static @NonNull ListenableFuture<List<Surface>> surfaceListWithTimeout(
57             @NonNull Collection<DeferrableSurface> deferrableSurfaces,
58             boolean removeNullSurfaces, long timeoutMillis, @NonNull Executor executor,
59             @NonNull ScheduledExecutorService scheduledExecutorService) {
60         List<ListenableFuture<Surface>> list = new ArrayList<>();
61         for (DeferrableSurface surface : deferrableSurfaces) {
62             list.add(Futures.nonCancellationPropagating(surface.getSurface()));
63         }
64         ListenableFuture<List<Surface>> listenableFuture = Futures.makeTimeoutFuture(
65                 timeoutMillis, scheduledExecutorService, Futures.successfulAsList(list)
66         );
67 
68         return CallbackToFutureAdapter.getFuture(completer -> {
69             // Cancel the listenableFuture if the outer task was cancelled, and the
70             // listenableFuture will cancel the scheduledFuture on its complete callback.
71             completer.addCancellationListener(() -> listenableFuture.cancel(true), executor);
72 
73             Futures.addCallback(listenableFuture, new FutureCallback<List<Surface>>() {
74                 @Override
75                 public void onSuccess(@Nullable List<Surface> result) {
76                     Preconditions.checkNotNull(result);
77                     List<Surface> surfaces = new ArrayList<>(result);
78                     if (removeNullSurfaces) {
79                         surfaces.removeAll(Collections.singleton(null));
80                     }
81                     completer.set(surfaces);
82                 }
83 
84                 @Override
85                 public void onFailure(@NonNull Throwable t) {
86                     if (t instanceof TimeoutException) {
87                         completer.setException(t);
88                     } else {
89                         completer.set(Collections.emptyList());
90                     }
91                 }
92             }, executor);
93 
94             return "surfaceList[" + deferrableSurfaces + "]";
95         });
96     }
97 
98     /**
99      * Attempts to increment the usage count of all surfaces in the given surface list.
100      *
101      * <p>If any usage count fails to increment (due to the surface already being closed), then
102      * none of the surfaces in the list will have their usage count incremented.
103      *
104      * @param surfaceList The list of surfaces whose usage count should be incremented.
105      * @return {@code true} if all usage counts were successfully incremented, {@code false}
106      * otherwise.
107      */
tryIncrementAll(@onNull List<DeferrableSurface> surfaceList)108     public static boolean tryIncrementAll(@NonNull List<DeferrableSurface> surfaceList) {
109         try {
110             incrementAll(surfaceList);
111         } catch (DeferrableSurface.SurfaceClosedException e) {
112             return false;
113         }
114 
115         return true;
116     }
117 
118     /**
119      * Attempts to increment the usage count of all surfaces in the given surface list.
120      *
121      * <p>If any usage count fails to increment (due to the surface already being closed), then
122      * none of the surfaces in the list will have their usage count incremented and an exception
123      * will be thrown.
124      *
125      * @param surfaceList The list of surfaces whose usage count should be incremented.
126      * @throws DeferrableSurface.SurfaceClosedException Containing the surface that failed to
127      *                                                  increment.
128      */
incrementAll(@onNull List<DeferrableSurface> surfaceList)129     public static void incrementAll(@NonNull List<DeferrableSurface> surfaceList)
130             throws DeferrableSurface.SurfaceClosedException {
131         if (!surfaceList.isEmpty()) {
132             int i = 0;
133             try {
134                 do {
135                     surfaceList.get(i).incrementUseCount();
136 
137                     // Successfully incremented.
138                     i++;
139                 } while (i < surfaceList.size());
140             } catch (DeferrableSurface.SurfaceClosedException e) {
141                 // Didn't successfully increment all usages, decrement those which were incremented.
142                 for (i = i - 1; i >= 0; --i) {
143                     surfaceList.get(i).decrementUseCount();
144                 }
145 
146                 // Rethrow the exception containing the surface that failed.
147                 throw e;
148             }
149         }
150     }
151 
152     /**
153      * Decrements the usage counts of every surface in the provided list.
154      *
155      * @param surfaceList The list of surfaces whose usage count should be decremented.
156      */
decrementAll(@onNull List<DeferrableSurface> surfaceList)157     public static void decrementAll(@NonNull List<DeferrableSurface> surfaceList) {
158         for (DeferrableSurface surface : surfaceList) {
159             surface.decrementUseCount();
160         }
161     }
162 }
163