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 androidx.camera.core.Logger;
20 
21 import org.jspecify.annotations.NonNull;
22 import org.jspecify.annotations.Nullable;
23 
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 
31 /**
32  * Collection of use cases which are attached to a specific camera.
33  *
34  * <p>This class tracks the current state of activity for each use case. There are two states that
35  * the use case can be in: attached and active. Attached means the use case is currently ready for
36  * the camera capture, but not currently capturing. Active means the use case is either currently
37  * issuing a capture request or one has already been issued.
38  */
39 public final class UseCaseAttachState {
40     private static final String TAG = "UseCaseAttachState";
41     /** The name of the camera the use cases are attached to. */
42     private final String mCameraId;
43     /** A map of the use cases to the corresponding state information. */
44     // Use LinkedHashMap to retain the attached order for bug fixing and unit testing.
45     private final Map<String, UseCaseAttachInfo> mAttachedUseCasesToInfoMap = new LinkedHashMap<>();
46 
47     /** Constructs an instance of the attach state which corresponds to the named camera. */
UseCaseAttachState(@onNull String cameraId)48     public UseCaseAttachState(@NonNull String cameraId) {
49         mCameraId = cameraId;
50     }
51 
52     /**
53      * Sets the use case to an active state.
54      *
55      * <p>Adds the use case to the collection if not already in it.
56      */
setUseCaseActive( @onNull String useCaseId, @NonNull SessionConfig sessionConfig, @NonNull UseCaseConfig<?> useCaseConfig, @Nullable StreamSpec streamSpec, @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes)57     public void setUseCaseActive(
58             @NonNull String useCaseId,
59             @NonNull SessionConfig sessionConfig,
60             @NonNull UseCaseConfig<?> useCaseConfig,
61             @Nullable StreamSpec streamSpec,
62             @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes) {
63         UseCaseAttachInfo useCaseAttachInfo = getOrCreateUseCaseAttachInfo(useCaseId,
64                 sessionConfig, useCaseConfig, streamSpec, captureTypes);
65         useCaseAttachInfo.setActive(true);
66     }
67 
68     /**
69      * Sets the use case to an inactive state.
70      *
71      * <p>Removes the use case from the collection if also offline.
72      */
setUseCaseInactive(@onNull String useCaseId)73     public void setUseCaseInactive(@NonNull String useCaseId) {
74         if (!mAttachedUseCasesToInfoMap.containsKey(useCaseId)) {
75             return;
76         }
77 
78         UseCaseAttachInfo useCaseAttachInfo = mAttachedUseCasesToInfoMap.get(useCaseId);
79         useCaseAttachInfo.setActive(false);
80         if (!useCaseAttachInfo.getAttached()) {
81             mAttachedUseCasesToInfoMap.remove(useCaseId);
82         }
83     }
84 
85     /**
86      * Sets the use case to an attached state.
87      *
88      * <p>Adds the use case to the collection if not already in it.
89      */
setUseCaseAttached(@onNull String useCaseId, @NonNull SessionConfig sessionConfig, @NonNull UseCaseConfig<?> useCaseConfig, @Nullable StreamSpec streamSpec, @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes)90     public void setUseCaseAttached(@NonNull String useCaseId,
91             @NonNull SessionConfig sessionConfig,
92             @NonNull UseCaseConfig<?> useCaseConfig,
93             @Nullable StreamSpec streamSpec,
94             @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes) {
95         UseCaseAttachInfo useCaseAttachInfo = getOrCreateUseCaseAttachInfo(useCaseId,
96                 sessionConfig, useCaseConfig, streamSpec, captureTypes);
97         useCaseAttachInfo.setAttached(true);
98 
99         // use case may become active before being attached, some info may have changed after
100         // the active state notification
101         updateUseCase(useCaseId, sessionConfig, useCaseConfig, streamSpec, captureTypes);
102     }
103 
104     /**
105      * Sets the use case to an detached state.
106      *
107      * <p>Removes the use case from the collection if also inactive.
108      */
setUseCaseDetached(@onNull String useCaseId)109     public void setUseCaseDetached(@NonNull String useCaseId) {
110         if (!mAttachedUseCasesToInfoMap.containsKey(useCaseId)) {
111             return;
112         }
113         UseCaseAttachInfo useCaseAttachInfo = mAttachedUseCasesToInfoMap.get(useCaseId);
114         useCaseAttachInfo.setAttached(false);
115         if (!useCaseAttachInfo.getActive()) {
116             mAttachedUseCasesToInfoMap.remove(useCaseId);
117         }
118     }
119 
120     /** Returns if the use case is attached or not. */
isUseCaseAttached(@onNull String useCaseId)121     public boolean isUseCaseAttached(@NonNull String useCaseId) {
122         if (!mAttachedUseCasesToInfoMap.containsKey(useCaseId)) {
123             return false;
124         }
125 
126         UseCaseAttachInfo useCaseAttachInfo = mAttachedUseCasesToInfoMap.get(useCaseId);
127         return useCaseAttachInfo.getAttached();
128     }
129 
getAttachedUseCaseConfigs()130     public @NonNull Collection<UseCaseConfig<?>> getAttachedUseCaseConfigs() {
131         return Collections.unmodifiableCollection(
132                 getUseCaseConfigs((useCaseAttachInfo) -> useCaseAttachInfo.getAttached()));
133     }
134 
getAttachedSessionConfigs()135     public @NonNull Collection<SessionConfig> getAttachedSessionConfigs() {
136         return Collections.unmodifiableCollection(
137                 getSessionConfigs((useCaseAttachInfo) -> useCaseAttachInfo.getAttached()));
138     }
139 
getAttachedUseCaseInfo()140     public @NonNull Collection<UseCaseAttachInfo> getAttachedUseCaseInfo() {
141         return Collections.unmodifiableCollection(
142                 getUseCaseInfo((useCaseAttachInfo) -> useCaseAttachInfo.getAttached()));
143     }
144 
getActiveAndAttachedSessionConfigs()145     public @NonNull Collection<SessionConfig> getActiveAndAttachedSessionConfigs() {
146         return Collections.unmodifiableCollection(
147                 getSessionConfigs((useCaseAttachInfo) ->
148                         useCaseAttachInfo.getActive() && useCaseAttachInfo.getAttached()));
149     }
150 
151     /**
152      * Updates the session configuration for a use case.
153      *
154      * <p>If the use case is not already in the collection, nothing is done.
155      */
updateUseCase( @onNull String useCaseId, @NonNull SessionConfig sessionConfig, @NonNull UseCaseConfig<?> useCaseConfig, @Nullable StreamSpec streamSpec, @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes)156     public void updateUseCase(
157             @NonNull String useCaseId,
158             @NonNull SessionConfig sessionConfig,
159             @NonNull UseCaseConfig<?> useCaseConfig,
160             @Nullable StreamSpec streamSpec,
161             @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes) {
162         if (!mAttachedUseCasesToInfoMap.containsKey(useCaseId)) {
163             return;
164         }
165 
166         // Rebuild the attach info from scratch to get the updated SessionConfig.
167         UseCaseAttachInfo newUseCaseAttachInfo =
168                 new UseCaseAttachInfo(sessionConfig, useCaseConfig, streamSpec, captureTypes);
169 
170         // Retain the attached and active flags.
171         UseCaseAttachInfo oldUseCaseAttachInfo = mAttachedUseCasesToInfoMap.get(useCaseId);
172         newUseCaseAttachInfo.setAttached(oldUseCaseAttachInfo.getAttached());
173         newUseCaseAttachInfo.setActive(oldUseCaseAttachInfo.getActive());
174         mAttachedUseCasesToInfoMap.put(useCaseId, newUseCaseAttachInfo);
175     }
176 
177     /**
178      * Removes the item from the map.
179      */
removeUseCase(@onNull String useCaseId)180     public void removeUseCase(@NonNull String useCaseId) {
181         mAttachedUseCasesToInfoMap.remove(useCaseId);
182     }
183 
184     /** Returns a session configuration builder for use cases which are both active and attached. */
getActiveAndAttachedBuilder()185     public SessionConfig.@NonNull ValidatingBuilder getActiveAndAttachedBuilder() {
186         SessionConfig.ValidatingBuilder validatingBuilder = new SessionConfig.ValidatingBuilder();
187 
188         List<String> list = new ArrayList<>();
189         for (Map.Entry<String, UseCaseAttachInfo> attachedUseCase :
190                 mAttachedUseCasesToInfoMap.entrySet()) {
191             UseCaseAttachInfo useCaseAttachInfo = attachedUseCase.getValue();
192             if (useCaseAttachInfo.getActive() && useCaseAttachInfo.getAttached()) {
193                 String useCaseId = attachedUseCase.getKey();
194                 validatingBuilder.add(useCaseAttachInfo.getSessionConfig());
195                 list.add(useCaseId);
196             }
197         }
198         Logger.d(TAG, "Active and attached use case: " + list + " for camera: " + mCameraId);
199         return validatingBuilder;
200     }
201 
202     /** Returns a session configuration builder for use cases which are attached. */
getAttachedBuilder()203     public SessionConfig.@NonNull ValidatingBuilder getAttachedBuilder() {
204         SessionConfig.ValidatingBuilder validatingBuilder = new SessionConfig.ValidatingBuilder();
205         List<String> list = new ArrayList<>();
206         for (Map.Entry<String, UseCaseAttachInfo> attachedUseCase :
207                 mAttachedUseCasesToInfoMap.entrySet()) {
208             UseCaseAttachInfo useCaseAttachInfo = attachedUseCase.getValue();
209             if (useCaseAttachInfo.getAttached()) {
210                 validatingBuilder.add(useCaseAttachInfo.getSessionConfig());
211                 String useCaseId = attachedUseCase.getKey();
212                 list.add(useCaseId);
213             }
214         }
215         Logger.d(TAG, "All use case: " + list + " for camera: " + mCameraId);
216         return validatingBuilder;
217     }
218 
getOrCreateUseCaseAttachInfo( @onNull String useCaseId, @NonNull SessionConfig sessionConfig, @NonNull UseCaseConfig<?> useCaseConfig, @Nullable StreamSpec streamSpec, @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes)219     private UseCaseAttachInfo getOrCreateUseCaseAttachInfo(
220             @NonNull String useCaseId,
221             @NonNull SessionConfig sessionConfig,
222             @NonNull UseCaseConfig<?> useCaseConfig,
223             @Nullable StreamSpec streamSpec,
224             @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes) {
225         UseCaseAttachInfo useCaseAttachInfo = mAttachedUseCasesToInfoMap.get(useCaseId);
226         if (useCaseAttachInfo == null) {
227             useCaseAttachInfo = new UseCaseAttachInfo(sessionConfig, useCaseConfig, streamSpec,
228                     captureTypes);
229             mAttachedUseCasesToInfoMap.put(useCaseId, useCaseAttachInfo);
230         }
231         return useCaseAttachInfo;
232     }
233 
getSessionConfigs(AttachStateFilter attachStateFilter)234     private Collection<SessionConfig> getSessionConfigs(AttachStateFilter attachStateFilter) {
235         List<SessionConfig> sessionConfigs = new ArrayList<>();
236         for (Map.Entry<String, UseCaseAttachInfo> attachedUseCase :
237                 mAttachedUseCasesToInfoMap.entrySet()) {
238             if (attachStateFilter == null || attachStateFilter.filter(attachedUseCase.getValue())) {
239                 sessionConfigs.add(attachedUseCase.getValue().getSessionConfig());
240             }
241         }
242         return sessionConfigs;
243     }
244 
getUseCaseConfigs(AttachStateFilter attachStateFilter)245     private Collection<UseCaseConfig<?>> getUseCaseConfigs(AttachStateFilter attachStateFilter) {
246         List<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
247         for (Map.Entry<String, UseCaseAttachInfo> attachedUseCase :
248                 mAttachedUseCasesToInfoMap.entrySet()) {
249             if (attachStateFilter == null || attachStateFilter.filter(attachedUseCase.getValue())) {
250                 useCaseConfigs.add(attachedUseCase.getValue().getUseCaseConfig());
251             }
252         }
253         return useCaseConfigs;
254     }
255 
getUseCaseInfo(AttachStateFilter attachStateFilter)256     private Collection<UseCaseAttachInfo> getUseCaseInfo(AttachStateFilter attachStateFilter) {
257         List<UseCaseAttachInfo> useCaseAttachInfo = new ArrayList<>();
258         for (Map.Entry<String, UseCaseAttachInfo> attachedUseCase :
259                 mAttachedUseCasesToInfoMap.entrySet()) {
260             if (attachStateFilter == null || attachStateFilter.filter(attachedUseCase.getValue())) {
261                 useCaseAttachInfo.add(attachedUseCase.getValue());
262             }
263         }
264         return useCaseAttachInfo;
265     }
266 
267     private interface AttachStateFilter {
filter(UseCaseAttachInfo attachInfo)268         boolean filter(UseCaseAttachInfo attachInfo);
269     }
270 
271     /** The set of state and configuration information for an attached use case. */
272     public static final class UseCaseAttachInfo {
273         /** The configurations required of the camera for the use case. */
274         private final @NonNull SessionConfig mSessionConfig;
275 
276         private final @NonNull UseCaseConfig<?> mUseCaseConfig;
277 
278         private final @Nullable StreamSpec mStreamSpec;
279 
280         private final @Nullable List<UseCaseConfigFactory.CaptureType> mCaptureTypes;
281 
282         /**
283          * True if the use case is currently attached (i.e. camera should have a capture session
284          * configured for it).
285          */
286         private boolean mAttached = false;
287 
288         /**
289          * True if the use case is currently active (i.e. camera should be issuing capture requests
290          * for it).
291          */
292         private boolean mActive = false;
293 
UseCaseAttachInfo(@onNull SessionConfig sessionConfig, @NonNull UseCaseConfig<?> useCaseConfig, @Nullable StreamSpec streamSpec, @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes)294         UseCaseAttachInfo(@NonNull SessionConfig sessionConfig,
295                 @NonNull UseCaseConfig<?> useCaseConfig,
296                 @Nullable StreamSpec streamSpec,
297                 @Nullable List<UseCaseConfigFactory.CaptureType> captureTypes) {
298             mSessionConfig = sessionConfig;
299             mUseCaseConfig = useCaseConfig;
300             mStreamSpec = streamSpec;
301             mCaptureTypes = captureTypes;
302         }
303 
getUseCaseConfig()304         public @NonNull UseCaseConfig<?> getUseCaseConfig() {
305             return mUseCaseConfig;
306         }
307 
getSessionConfig()308         public @NonNull SessionConfig getSessionConfig() {
309             return mSessionConfig;
310         }
311 
getStreamSpec()312         public @Nullable StreamSpec getStreamSpec() {
313             return mStreamSpec;
314         }
315 
getCaptureTypes()316         public @Nullable List<UseCaseConfigFactory.CaptureType> getCaptureTypes() {
317             return mCaptureTypes;
318         }
319 
getAttached()320         boolean getAttached() {
321             return mAttached;
322         }
323 
setAttached(boolean attached)324         void setAttached(boolean attached) {
325             mAttached = attached;
326         }
327 
getActive()328         boolean getActive() {
329             return mActive;
330         }
331 
setActive(boolean active)332         void setActive(boolean active) {
333             mActive = active;
334         }
335 
336         @SuppressWarnings("ObjectToString")
337         @Override
toString()338         public @NonNull String toString() {
339             return "UseCaseAttachInfo{" + "mSessionConfig=" + mSessionConfig + ", mUseCaseConfig="
340                     + mUseCaseConfig + ", mStreamSpec=" + mStreamSpec + ", mCaptureTypes="
341                     + mCaptureTypes + ", mAttached=" + mAttached + ", mActive=" + mActive + '}';
342         }
343     }
344 }
345