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