1 /* 2 * Copyright (C) 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; 18 19 import static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF; 20 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON; 21 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY; 22 import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED; 23 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_MAX_RESOLUTION; 24 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_RESOLUTION_SELECTOR; 25 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO; 26 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_TARGET_RESOLUTION; 27 import static androidx.camera.core.impl.StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED; 28 import static androidx.camera.core.impl.utils.TransformUtils.within360; 29 import static androidx.camera.core.processing.TargetUtils.isSuperset; 30 import static androidx.core.util.Preconditions.checkArgument; 31 import static androidx.core.util.Preconditions.checkArgumentInRange; 32 33 import android.annotation.SuppressLint; 34 import android.graphics.Matrix; 35 import android.graphics.Rect; 36 import android.media.ImageReader; 37 import android.util.Range; 38 import android.util.Size; 39 import android.view.OrientationEventListener; 40 import android.view.Surface; 41 42 import androidx.annotation.CallSuper; 43 import androidx.annotation.GuardedBy; 44 import androidx.annotation.IntRange; 45 import androidx.annotation.RestrictTo; 46 import androidx.annotation.RestrictTo.Scope; 47 import androidx.camera.core.impl.CameraControlInternal; 48 import androidx.camera.core.impl.CameraInfoInternal; 49 import androidx.camera.core.impl.CameraInternal; 50 import androidx.camera.core.impl.Config; 51 import androidx.camera.core.impl.Config.Option; 52 import androidx.camera.core.impl.DeferrableSurface; 53 import androidx.camera.core.impl.ImageOutputConfig; 54 import androidx.camera.core.impl.MutableOptionsBundle; 55 import androidx.camera.core.impl.SessionConfig; 56 import androidx.camera.core.impl.StreamSpec; 57 import androidx.camera.core.impl.UseCaseConfig; 58 import androidx.camera.core.impl.UseCaseConfigFactory; 59 import androidx.camera.core.internal.TargetConfig; 60 import androidx.camera.core.internal.compat.quirk.AeFpsRangeQuirk; 61 import androidx.camera.core.internal.utils.UseCaseConfigUtil; 62 import androidx.camera.core.resolutionselector.ResolutionSelector; 63 import androidx.camera.core.streamsharing.StreamSharing; 64 import androidx.core.util.Preconditions; 65 66 import org.jspecify.annotations.NonNull; 67 import org.jspecify.annotations.Nullable; 68 69 import java.util.Collections; 70 import java.util.HashSet; 71 import java.util.List; 72 import java.util.Objects; 73 import java.util.Set; 74 75 /** 76 * The use case which all other use cases are built on top of. 77 * 78 * <p>A UseCase provides functionality to map the set of arguments in a use case to arguments 79 * that are usable by a camera. UseCase also will communicate of the active/inactive state to 80 * the Camera. 81 */ 82 public abstract class UseCase { 83 84 //////////////////////////////////////////////////////////////////////////////////////////// 85 // [UseCase lifetime constant] - Stays constant for the lifetime of the UseCase. Which means 86 // they could be created in the constructor. 87 //////////////////////////////////////////////////////////////////////////////////////////// 88 89 /** 90 * The set of {@link StateChangeCallback} that are currently listening state transitions of this 91 * use case. 92 */ 93 private final Set<StateChangeCallback> mStateChangeCallbacks = new HashSet<>(); 94 95 private final Object mCameraLock = new Object(); 96 97 //////////////////////////////////////////////////////////////////////////////////////////// 98 // [UseCase lifetime dynamic] - Dynamic variables which could change during anytime during 99 // the UseCase lifetime. 100 //////////////////////////////////////////////////////////////////////////////////////////// 101 102 private State mState = State.INACTIVE; 103 104 /** Extended config, applied on top of the app defined Config (mUseCaseConfig). */ 105 private @Nullable UseCaseConfig<?> mExtendedConfig; 106 107 /** 108 * Store the app defined {@link UseCaseConfig} used to create the use case. 109 */ 110 private @NonNull UseCaseConfig<?> mUseCaseConfig; 111 112 /** 113 * The currently used Config. 114 * 115 * <p> This is the combination of the extended Config, app provided Config, and camera 116 * implementation Config (with decreasing priority). 117 */ 118 private @NonNull UseCaseConfig<?> mCurrentConfig; 119 120 //////////////////////////////////////////////////////////////////////////////////////////// 121 // [UseCase attached constant] - Is only valid when the UseCase is attached to a camera. 122 //////////////////////////////////////////////////////////////////////////////////////////// 123 124 /** 125 * The {@link StreamSpec} assigned to the {@link UseCase} based on the attached camera. 126 */ 127 private StreamSpec mAttachedStreamSpec; 128 129 /** 130 * The camera implementation provided Config. Its options has lowest priority and will be 131 * overwritten by any app defined or extended configs. 132 */ 133 private @Nullable UseCaseConfig<?> mCameraConfig; 134 135 /** 136 * The crop rect calculated at the time of binding based on {@link ViewPort}. 137 */ 138 private @Nullable Rect mViewPortCropRect; 139 140 /** 141 * The sensor to image buffer transform matrix. 142 */ 143 private @NonNull Matrix mSensorToBufferTransformMatrix = new Matrix(); 144 145 @GuardedBy("mCameraLock") 146 private CameraInternal mCamera; 147 148 @GuardedBy("mCameraLock") 149 private @Nullable CameraInternal mSecondaryCamera; 150 151 private @Nullable CameraEffect mEffect; 152 153 private @Nullable String mPhysicalCameraId; 154 155 //////////////////////////////////////////////////////////////////////////////////////////// 156 // [UseCase attached dynamic] - Can change but is only available when the UseCase is attached. 157 //////////////////////////////////////////////////////////////////////////////////////////// 158 159 // The currently attached session config 160 private @NonNull SessionConfig mAttachedSessionConfig = 161 SessionConfig.defaultEmptySessionConfig(); 162 163 // The currently attached session config for secondary camera in dual camera case 164 private @NonNull SessionConfig mAttachedSecondarySessionConfig = 165 SessionConfig.defaultEmptySessionConfig(); 166 167 /** 168 * Creates a named instance of the use case. 169 * 170 * @param currentConfig the configuration object used for this use case 171 */ 172 @RestrictTo(Scope.LIBRARY_GROUP) UseCase(@onNull UseCaseConfig<?> currentConfig)173 protected UseCase(@NonNull UseCaseConfig<?> currentConfig) { 174 mUseCaseConfig = currentConfig; 175 mCurrentConfig = currentConfig; 176 } 177 178 /** 179 * Retrieve the default {@link UseCaseConfig} for the UseCase. 180 * 181 * @param applyDefaultConfig true if this is the base config applied to a UseCase. 182 * @param factory the factory that contains the default UseCases. 183 * @return The UseCaseConfig or null if there is no default Config. 184 */ 185 @RestrictTo(Scope.LIBRARY_GROUP) getDefaultConfig(boolean applyDefaultConfig, @NonNull UseCaseConfigFactory factory)186 public abstract @Nullable UseCaseConfig<?> getDefaultConfig(boolean applyDefaultConfig, 187 @NonNull UseCaseConfigFactory factory); 188 189 /** 190 * Create a {@link UseCaseConfig.Builder} for the UseCase. 191 * 192 * @param config the Config to initialize the builder 193 */ 194 @RestrictTo(Scope.LIBRARY_GROUP) getUseCaseConfigBuilder( @onNull Config config)195 public abstract UseCaseConfig.@NonNull Builder<?, ?, ?> getUseCaseConfigBuilder( 196 @NonNull Config config); 197 198 /** 199 * Create a merged {@link UseCaseConfig} from the UseCase, camera, and an extended config. 200 * 201 * @param cameraInfo info about the camera which may be used to resolve conflicts. 202 * @param extendedConfig configs that take priority over the UseCase's default config 203 * @param cameraDefaultConfig configs that have lower priority than the UseCase's default. 204 * This Config comes from the camera implementation. 205 * @throws IllegalArgumentException if there exists conflicts in the merged config that can 206 * not be resolved 207 */ 208 @RestrictTo(Scope.LIBRARY_GROUP) mergeConfigs( @onNull CameraInfoInternal cameraInfo, @Nullable UseCaseConfig<?> extendedConfig, @Nullable UseCaseConfig<?> cameraDefaultConfig)209 public @NonNull UseCaseConfig<?> mergeConfigs( 210 @NonNull CameraInfoInternal cameraInfo, 211 @Nullable UseCaseConfig<?> extendedConfig, 212 @Nullable UseCaseConfig<?> cameraDefaultConfig) { 213 MutableOptionsBundle mergedConfig; 214 215 if (cameraDefaultConfig != null) { 216 mergedConfig = MutableOptionsBundle.from(cameraDefaultConfig); 217 mergedConfig.removeOption(TargetConfig.OPTION_TARGET_NAME); 218 } else { 219 mergedConfig = MutableOptionsBundle.create(); 220 } 221 222 // Removes the default resolution selector setting to go for the legacy resolution 223 // selection logic flow if applications call the legacy setTargetAspectRatio and 224 // setTargetResolution APIs to do the setting. 225 if (mUseCaseConfig.containsOption(OPTION_TARGET_ASPECT_RATIO) 226 || mUseCaseConfig.containsOption(OPTION_TARGET_RESOLUTION)) { 227 if (mergedConfig.containsOption(OPTION_RESOLUTION_SELECTOR)) { 228 mergedConfig.removeOption(OPTION_RESOLUTION_SELECTOR); 229 } 230 } 231 232 // Removes the default max resolution setting if application sets any ResolutionStrategy 233 // to override it. 234 if (mUseCaseConfig.containsOption(OPTION_RESOLUTION_SELECTOR) 235 && mergedConfig.containsOption(OPTION_MAX_RESOLUTION)) { 236 ResolutionSelector resolutionSelector = 237 mUseCaseConfig.retrieveOption(OPTION_RESOLUTION_SELECTOR); 238 if (resolutionSelector.getResolutionStrategy() != null) { 239 mergedConfig.removeOption(OPTION_MAX_RESOLUTION); 240 } 241 } 242 243 // If any options need special handling, this is the place to do it. For now we'll just copy 244 // over all options. 245 for (Option<?> opt : mUseCaseConfig.listOptions()) { 246 Config.mergeOptionValue(mergedConfig, mergedConfig, mUseCaseConfig, opt); 247 } 248 249 if (extendedConfig != null) { 250 // If any options need special handling, this is the place to do it. For now we'll 251 // just copy over all options. 252 for (Option<?> opt : extendedConfig.listOptions()) { 253 @SuppressWarnings("unchecked") // Options/values are being copied directly 254 Option<Object> objectOpt = (Option<Object>) opt; 255 if (objectOpt.getId().equals(TargetConfig.OPTION_TARGET_NAME.getId())) { 256 continue; 257 } 258 Config.mergeOptionValue(mergedConfig, mergedConfig, extendedConfig, opt); 259 } 260 } 261 262 // If OPTION_TARGET_RESOLUTION has been set by the user, remove 263 // OPTION_TARGET_ASPECT_RATIO from defaultConfigBuilder because these two settings cannot be 264 // set at the same time. 265 if (mergedConfig.containsOption(ImageOutputConfig.OPTION_TARGET_RESOLUTION) 266 && mergedConfig.containsOption( 267 ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO)) { 268 mergedConfig.removeOption(ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO); 269 } 270 271 // Forces disable ZSL when high resolution is enabled. 272 if (mergedConfig.containsOption(ImageOutputConfig.OPTION_RESOLUTION_SELECTOR) 273 && mergedConfig.retrieveOption( 274 ImageOutputConfig.OPTION_RESOLUTION_SELECTOR).getAllowedResolutionMode() 275 != ResolutionSelector.PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION) { 276 mergedConfig.insertOption(UseCaseConfig.OPTION_ZSL_DISABLED, true); 277 } 278 279 return onMergeConfig(cameraInfo, getUseCaseConfigBuilder(mergedConfig)); 280 } 281 282 /** 283 * Called when a set of configs are merged so the UseCase can do additional handling. 284 * 285 * <p> This can be overridden by a UseCase which need to do additional verification of the 286 * configs to make sure there are no conflicting options. 287 * 288 * @param cameraInfo info about the camera which may be used to resolve conflicts. 289 * @param builder the builder containing the merged configs requiring addition conflict 290 * resolution 291 * @return the conflict resolved config 292 * @throws IllegalArgumentException if there exists conflicts in the merged config that can 293 * not be resolved 294 */ 295 @RestrictTo(Scope.LIBRARY_GROUP) onMergeConfig(@onNull CameraInfoInternal cameraInfo, UseCaseConfig.@NonNull Builder<?, ?, ?> builder)296 protected @NonNull UseCaseConfig<?> onMergeConfig(@NonNull CameraInfoInternal cameraInfo, 297 UseCaseConfig.@NonNull Builder<?, ?, ?> builder) { 298 return builder.getUseCaseConfig(); 299 } 300 301 /** 302 * A utility function that can convert the orientation degrees of 303 * {@link OrientationEventListener} to the nearest {@link Surface} rotation. 304 * 305 * <p>In general, it is best to use an {@link android.view.OrientationEventListener} to set 306 * the UseCase target rotation. This way, the rotation output will indicate which way is down 307 * for a given image or video. This is important since display orientation may be locked by 308 * device default, user setting, or app configuration, and some devices may not transition to a 309 * reverse-portrait display orientation. In these cases, set target rotation dynamically 310 * according to the {@link android.view.OrientationEventListener}, without re-creating the 311 * use case. The sample code is as below: 312 * <pre>{@code 313 * public class CameraXActivity extends AppCompatActivity { 314 * 315 * private OrientationEventListener mOrientationEventListener; 316 * 317 * @Override 318 * protected void onStart() { 319 * super.onStart(); 320 * if (mOrientationEventListener == null) { 321 * mOrientationEventListener = new OrientationEventListener(this) { 322 * @Override 323 * public void onOrientationChanged(int orientation) { 324 * if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { 325 * return; 326 * } 327 * int rotation = UseCase.snapToSurfaceRotation(orientation); 328 * mImageCapture.setTargetRotation(rotation); 329 * mImageAnalysis.setTargetRotation(rotation); 330 * mVideoCapture.setTargetRotation(rotation); 331 * } 332 * }; 333 * } 334 * mOrientationEventListener.enable(); 335 * } 336 * 337 * @Override 338 * protected void onStop() { 339 * super.onStop(); 340 * mOrientationEventListener.disable(); 341 * } 342 * } 343 * }</pre> 344 * 345 * @param orientation the orientation degrees in range [0, 359]. 346 * @return surface rotation. One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, 347 * {@link Surface#ROTATION_180} and {@link Surface#ROTATION_270}. 348 * @throws IllegalArgumentException if the input orientation degrees is not in range [0, 359]. 349 * @see ImageCapture#setTargetRotation(int) 350 * @see ImageAnalysis#setTargetRotation(int) 351 */ 352 @ImageOutputConfig.RotationValue snapToSurfaceRotation(@ntRangefrom = 0, to = 359) int orientation)353 public static int snapToSurfaceRotation(@IntRange(from = 0, to = 359) int orientation) { 354 checkArgumentInRange(orientation, 0, 359, "orientation"); 355 if (orientation >= 315 || orientation < 45) { 356 return Surface.ROTATION_0; 357 } else if (orientation >= 225) { 358 return Surface.ROTATION_90; 359 } else if (orientation >= 135) { 360 return Surface.ROTATION_180; 361 } else { 362 return Surface.ROTATION_270; 363 } 364 } 365 366 @RestrictTo(Scope.LIBRARY_GROUP) setPhysicalCameraId(@onNull String physicalCameraId)367 public void setPhysicalCameraId(@NonNull String physicalCameraId) { 368 mPhysicalCameraId = physicalCameraId; 369 } 370 371 @RestrictTo(Scope.LIBRARY_GROUP) getPhysicalCameraId()372 public @Nullable String getPhysicalCameraId() { 373 return mPhysicalCameraId; 374 } 375 376 /** 377 * Updates the target rotation of the use case config. 378 * 379 * @param targetRotation Target rotation of the output image, expressed as one of 380 * {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, 381 * {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}. 382 * @return true if the target rotation was changed. 383 */ 384 @RestrictTo(Scope.LIBRARY_GROUP) setTargetRotationInternal( @mageOutputConfig.RotationValue int targetRotation)385 protected boolean setTargetRotationInternal( 386 @ImageOutputConfig.RotationValue int targetRotation) { 387 ImageOutputConfig oldConfig = (ImageOutputConfig) getCurrentConfig(); 388 int oldRotation = oldConfig.getTargetRotation(ImageOutputConfig.INVALID_ROTATION); 389 if (oldRotation == ImageOutputConfig.INVALID_ROTATION || oldRotation != targetRotation) { 390 UseCaseConfig.Builder<?, ?, ?> builder = getUseCaseConfigBuilder(mUseCaseConfig); 391 UseCaseConfigUtil.updateTargetRotationAndRelatedConfigs(builder, targetRotation); 392 mUseCaseConfig = builder.getUseCaseConfig(); 393 394 // Only merge configs if currently attached to a camera. Otherwise, set the current 395 // config to the use case config and mergeConfig() will be called once the use case 396 // is attached to a camera. 397 CameraInternal camera = getCamera(); 398 if (camera == null) { 399 mCurrentConfig = mUseCaseConfig; 400 } else { 401 mCurrentConfig = mergeConfigs(camera.getCameraInfoInternal(), mExtendedConfig, 402 mCameraConfig); 403 } 404 405 return true; 406 } 407 return false; 408 } 409 410 /** 411 * Returns the rotation that the intended target resolution is expressed in. 412 * 413 * @return The rotation of the intended target. 414 */ 415 @SuppressLint("WrongConstant") 416 @RestrictTo(Scope.LIBRARY_GROUP) 417 @ImageOutputConfig.RotationValue getTargetRotationInternal()418 protected int getTargetRotationInternal() { 419 return ((ImageOutputConfig) mCurrentConfig).getTargetRotation(Surface.ROTATION_0); 420 } 421 422 /** 423 * Returns the target frame rate range for the associated VideoCapture use case. 424 * 425 * @return The target frame rate. 426 */ 427 @RestrictTo(Scope.LIBRARY_GROUP) getTargetFrameRateInternal()428 protected @NonNull Range<Integer> getTargetFrameRateInternal() { 429 return mCurrentConfig.getTargetFrameRate(FRAME_RATE_RANGE_UNSPECIFIED); 430 } 431 432 /** 433 * Returns the mirror mode. 434 * 435 * <p>If mirror mode is not set, defaults to {@link MirrorMode#MIRROR_MODE_OFF}. 436 * 437 */ 438 @RestrictTo(Scope.LIBRARY_GROUP) 439 @MirrorMode.Mirror getMirrorModeInternal()440 protected int getMirrorModeInternal() { 441 return ((ImageOutputConfig) mCurrentConfig).getMirrorMode(MIRROR_MODE_UNSPECIFIED); 442 } 443 444 /** 445 * Returns if the mirroring is required with the associated camera. 446 * 447 */ 448 @RestrictTo(Scope.LIBRARY_GROUP) isMirroringRequired(@onNull CameraInternal camera)449 public boolean isMirroringRequired(@NonNull CameraInternal camera) { 450 int mirrorMode = getMirrorModeInternal(); 451 switch (mirrorMode) { 452 case MIRROR_MODE_UNSPECIFIED: 453 case MIRROR_MODE_OFF: 454 return false; 455 case MIRROR_MODE_ON: 456 return true; 457 case MIRROR_MODE_ON_FRONT_ONLY: 458 return camera.isFrontFacing(); 459 default: 460 throw new AssertionError("Unknown mirrorMode: " + mirrorMode); 461 } 462 } 463 464 /** 465 * Returns the target rotation set by apps explicitly. 466 * 467 * @return The rotation of the intended target. 468 */ 469 @RestrictTo(Scope.LIBRARY_GROUP) 470 @ImageOutputConfig.OptionalRotationValue getAppTargetRotation()471 protected int getAppTargetRotation() { 472 return ((ImageOutputConfig) mCurrentConfig) 473 .getAppTargetRotation(ImageOutputConfig.ROTATION_NOT_SPECIFIED); 474 } 475 476 /** 477 * Gets the relative rotation degrees without mirroring. 478 * 479 */ 480 @RestrictTo(Scope.LIBRARY_GROUP) 481 @IntRange(from = 0, to = 359) getRelativeRotation(@onNull CameraInternal cameraInternal)482 protected int getRelativeRotation(@NonNull CameraInternal cameraInternal) { 483 return getRelativeRotation(cameraInternal, /*requireMirroring=*/false); 484 } 485 486 /** 487 * Gets the relative rotation degrees given whether the output should be mirrored. 488 * 489 */ 490 @RestrictTo(Scope.LIBRARY_GROUP) 491 @IntRange(from = 0, to = 359) getRelativeRotation(@onNull CameraInternal cameraInternal, boolean requireMirroring)492 protected int getRelativeRotation(@NonNull CameraInternal cameraInternal, 493 boolean requireMirroring) { 494 int rotation = cameraInternal.getCameraInfoInternal().getSensorRotationDegrees( 495 getTargetRotationInternal()); 496 // Parent UseCase always mirror the stream if the child requires it. No camera transform 497 // means that the stream is copied by a parent, and if the child also requires mirroring, 498 // we know that the stream has been mirrored. 499 boolean inputStreamMirrored = !cameraInternal.getHasTransform() && requireMirroring; 500 if (inputStreamMirrored) { 501 // Flip rotation if the stream has been mirrored. 502 rotation = within360(-rotation); 503 } 504 return rotation; 505 } 506 507 /** 508 * Sets the {@link SessionConfig} that will be used by the attached {@link Camera}. 509 * 510 */ 511 @RestrictTo(Scope.LIBRARY_GROUP) updateSessionConfig(@onNull List<SessionConfig> sessionConfigs)512 protected void updateSessionConfig(@NonNull List<SessionConfig> sessionConfigs) { 513 if (sessionConfigs.isEmpty()) { 514 return; 515 } 516 517 mAttachedSessionConfig = sessionConfigs.get(0); 518 if (sessionConfigs.size() > 1) { 519 mAttachedSecondarySessionConfig = sessionConfigs.get(1); 520 } 521 522 for (SessionConfig sessionConfig : sessionConfigs) { 523 for (DeferrableSurface surface : sessionConfig.getSurfaces()) { 524 if (surface.getContainerClass() == null) { 525 surface.setContainerClass(this.getClass()); 526 } 527 } 528 } 529 } 530 531 /** 532 * Add a {@link StateChangeCallback}, which listens to this UseCase's active and inactive 533 * transition events. 534 */ addStateChangeCallback(@onNull StateChangeCallback callback)535 private void addStateChangeCallback(@NonNull StateChangeCallback callback) { 536 mStateChangeCallbacks.add(callback); 537 } 538 539 /** 540 * Remove a {@link StateChangeCallback} from listening to this UseCase's active and inactive 541 * transition events. 542 * 543 * <p>If the listener isn't currently listening to the UseCase then this call does nothing. 544 */ removeStateChangeCallback(@onNull StateChangeCallback callback)545 private void removeStateChangeCallback(@NonNull StateChangeCallback callback) { 546 mStateChangeCallbacks.remove(callback); 547 } 548 549 /** 550 * Get the current {@link SessionConfig}. 551 * 552 */ 553 @RestrictTo(Scope.LIBRARY_GROUP) getSessionConfig()554 public @NonNull SessionConfig getSessionConfig() { 555 return mAttachedSessionConfig; 556 } 557 558 /** 559 * Get the current {@link SessionConfig} of the secondary camera in dual camera case. 560 * 561 */ 562 @RestrictTo(Scope.LIBRARY_GROUP) getSecondarySessionConfig()563 public @NonNull SessionConfig getSecondarySessionConfig() { 564 return mAttachedSecondarySessionConfig; 565 } 566 567 /** 568 * Notify all {@link StateChangeCallback} that are listening to this UseCase that it has 569 * transitioned to an active state. 570 * 571 */ 572 @RestrictTo(Scope.LIBRARY_GROUP) notifyActive()573 protected final void notifyActive() { 574 mState = State.ACTIVE; 575 notifyState(); 576 } 577 578 /** 579 * Notify all {@link StateChangeCallback} that are listening to this UseCase that it has 580 * transitioned to an inactive state. 581 * 582 */ 583 @RestrictTo(Scope.LIBRARY_GROUP) notifyInactive()584 protected final void notifyInactive() { 585 mState = State.INACTIVE; 586 notifyState(); 587 } 588 589 /** 590 * Notify all {@link StateChangeCallback} that are listening to this UseCase that the 591 * settings have been updated. 592 * 593 */ 594 @RestrictTo(Scope.LIBRARY_GROUP) notifyUpdated()595 protected final void notifyUpdated() { 596 for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) { 597 stateChangeCallback.onUseCaseUpdated(this); 598 } 599 } 600 601 /** 602 * Notify all {@link StateChangeCallback} that are listening to this UseCase that the use 603 * case needs to be completely reset. 604 * 605 */ 606 @RestrictTo(Scope.LIBRARY_GROUP) notifyReset()607 protected final void notifyReset() { 608 for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) { 609 stateChangeCallback.onUseCaseReset(this); 610 } 611 } 612 613 /** 614 * Notify all {@link StateChangeCallback} that are listening to this UseCase of its current 615 * state. 616 * 617 */ 618 @RestrictTo(Scope.LIBRARY_GROUP) notifyState()619 public final void notifyState() { 620 switch (mState) { 621 case INACTIVE: 622 for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) { 623 stateChangeCallback.onUseCaseInactive(this); 624 } 625 break; 626 case ACTIVE: 627 for (StateChangeCallback stateChangeCallback : mStateChangeCallbacks) { 628 stateChangeCallback.onUseCaseActive(this); 629 } 630 break; 631 } 632 } 633 634 /** 635 * Returns the camera ID for the currently attached camera, or throws an exception if no 636 * camera is attached. 637 * 638 */ 639 @RestrictTo(Scope.LIBRARY_GROUP) getCameraId()640 protected @NonNull String getCameraId() { 641 return Preconditions.checkNotNull(getCamera(), 642 "No camera attached to use case: " + this).getCameraInfoInternal().getCameraId(); 643 } 644 645 /** 646 * Returns the camera ID for the currently attached secondary camera, or throws an exception if 647 * no camera is attached. 648 * 649 */ 650 @RestrictTo(Scope.LIBRARY_GROUP) getSecondaryCameraId()651 protected @Nullable String getSecondaryCameraId() { 652 return getSecondaryCamera() == null ? null : getSecondaryCamera() 653 .getCameraInfoInternal().getCameraId(); 654 } 655 656 /** 657 * Checks whether the provided camera ID is the currently attached camera ID. 658 * 659 */ 660 @RestrictTo(Scope.LIBRARY_GROUP) isCurrentCamera(@onNull String cameraId)661 protected boolean isCurrentCamera(@NonNull String cameraId) { 662 if (getCamera() == null) { 663 return false; 664 } 665 return Objects.equals(cameraId, getCameraId()); 666 } 667 668 @RestrictTo(Scope.LIBRARY_GROUP) getName()669 public @NonNull String getName() { 670 return Objects.requireNonNull( 671 mCurrentConfig.getTargetName("<UnknownUseCase-" + hashCode() + ">")); 672 } 673 674 /** 675 * Retrieves the configuration set by applications. 676 */ 677 @RestrictTo(Scope.LIBRARY_GROUP) getAppConfig()678 protected @NonNull UseCaseConfig<?> getAppConfig() { 679 return mUseCaseConfig; 680 } 681 682 /** 683 * Retrieves the configuration used by this use case. 684 * 685 * @return the configuration used by this use case. 686 */ 687 @RestrictTo(Scope.LIBRARY_GROUP) getCurrentConfig()688 public @NonNull UseCaseConfig<?> getCurrentConfig() { 689 return mCurrentConfig; 690 } 691 692 /** 693 * Returns the currently attached {@link Camera} or {@code null} if none is attached. 694 * 695 */ 696 @RestrictTo(Scope.LIBRARY_GROUP) getCamera()697 public @Nullable CameraInternal getCamera() { 698 synchronized (mCameraLock) { 699 return mCamera; 700 } 701 } 702 703 /** 704 * Returns the currently attached secondary {@link Camera} or {@code null} if none is attached. 705 * 706 */ 707 @RestrictTo(Scope.LIBRARY_GROUP) getSecondaryCamera()708 public @Nullable CameraInternal getSecondaryCamera() { 709 synchronized (mCameraLock) { 710 return mSecondaryCamera; 711 } 712 } 713 714 /** 715 * Retrieves the currently attached surface resolution. 716 * 717 * @return the currently attached surface resolution for the given camera id. 718 */ 719 @RestrictTo(Scope.LIBRARY_GROUP) getAttachedSurfaceResolution()720 public @Nullable Size getAttachedSurfaceResolution() { 721 return mAttachedStreamSpec != null ? mAttachedStreamSpec.getResolution() : null; 722 } 723 724 /** 725 * Retrieves the currently attached stream specification. 726 * 727 * @return the currently attached stream specification. 728 */ 729 @RestrictTo(Scope.LIBRARY_GROUP) getAttachedStreamSpec()730 public @Nullable StreamSpec getAttachedStreamSpec() { 731 return mAttachedStreamSpec; 732 } 733 734 /** 735 * Offers suggested stream specification for the UseCase. 736 * 737 */ 738 @RestrictTo(Scope.LIBRARY_GROUP) updateSuggestedStreamSpec( @onNull StreamSpec primaryStreamSpec, @Nullable StreamSpec secondaryStreamSpec)739 public void updateSuggestedStreamSpec( 740 @NonNull StreamSpec primaryStreamSpec, 741 @Nullable StreamSpec secondaryStreamSpec) { 742 mAttachedStreamSpec = onSuggestedStreamSpecUpdated( 743 primaryStreamSpec, secondaryStreamSpec); 744 } 745 746 /** 747 * Called when binding new use cases via {@code CameraX#bindToLifecycle(LifecycleOwner, 748 * CameraSelector, UseCase...)} with additional information for dual cameras. 749 * 750 * <p>Override to create necessary objects like {@link ImageReader} depending 751 * on the stream specification. 752 * 753 * @param primaryStreamSpec The suggested stream specification that depends on camera device 754 * capability and what and how many use cases will be bound. 755 * @param secondaryStreamSpec The suggested stream specification for secondary camera in 756 * dual camera case. 757 * @return The stream specification that finally used to create the SessionConfig to 758 * attach to the camera device. 759 */ 760 @RestrictTo(Scope.LIBRARY_GROUP) onSuggestedStreamSpecUpdated( @onNull StreamSpec primaryStreamSpec, @Nullable StreamSpec secondaryStreamSpec)761 protected @NonNull StreamSpec onSuggestedStreamSpecUpdated( 762 @NonNull StreamSpec primaryStreamSpec, 763 @Nullable StreamSpec secondaryStreamSpec) { 764 return primaryStreamSpec; 765 } 766 767 /** 768 * Update the implementation options of the stream specification for the UseCase. 769 * 770 */ 771 @RestrictTo(Scope.LIBRARY_GROUP) updateSuggestedStreamSpecImplementationOptions(@onNull Config config)772 public void updateSuggestedStreamSpecImplementationOptions(@NonNull Config config) { 773 // TODO(b/349823704): investigate whether we need mAttachedSecondaryStreamSpec for 774 // StreamSharing 775 mAttachedStreamSpec = onSuggestedStreamSpecImplementationOptionsUpdated(config); 776 } 777 778 /** 779 * Called when updating the stream specifications' implementation options of existing use cases 780 * via {@code CameraUseCaseAdapter#updateUseCases}. 781 * 782 * @param config The new implementationOptions for the stream specification. 783 */ 784 @RestrictTo(Scope.LIBRARY_GROUP) onSuggestedStreamSpecImplementationOptionsUpdated( @onNull Config config)785 protected @NonNull StreamSpec onSuggestedStreamSpecImplementationOptionsUpdated( 786 @NonNull Config config) { 787 if (mAttachedStreamSpec == null) { 788 throw new UnsupportedOperationException("Attempt to update the implementation options " 789 + "for a use case without attached stream specifications."); 790 } 791 return mAttachedStreamSpec.toBuilder().setImplementationOptions(config).build(); 792 } 793 794 795 /** 796 * Called when CameraControlInternal is attached into the UseCase. UseCase may need to 797 * override this method to configure the CameraControlInternal here. Ex. Setting correct flash 798 * mode by CameraControlInternal.setFlashMode to enable correct AE mode and flash state. 799 * 800 */ 801 @RestrictTo(Scope.LIBRARY_GROUP) onCameraControlReady()802 public void onCameraControlReady() { 803 } 804 805 /** 806 * Binds use case to a camera. 807 * 808 * <p>Before a use case can receive frame data, it needs to establish association with the 809 * target camera first. An implementation of {@link CameraInternal} (e.g. a lifecycle camera 810 * or lifecycle-less camera) is provided when 811 * {@link #bindToCamera(CameraInternal, CameraInternal, UseCaseConfig, UseCaseConfig)} is 812 * invoked, so that the use case can retrieve the necessary information from the camera 813 * to calculate and set up the configs. 814 * 815 * <p>The default, extended and camera config settings are also applied to the use case config 816 * in this stage. Subclasses can override {@link #onMergeConfig} to update the use case 817 * config for use case specific purposes. 818 * 819 * <p>Calling {@link #getCameraControl()} can retrieve a real {@link CameraControlInternal} 820 * implementation of the associated camera after this function is invoked. Otherwise, a fake 821 * no-op {@link CameraControlInternal} implementation is returned by 822 * {@link #getCameraControl()} function. 823 */ 824 @SuppressLint("WrongConstant") 825 @RestrictTo(Scope.LIBRARY_GROUP) bindToCamera(@onNull CameraInternal camera, @Nullable CameraInternal secondaryCamera, @Nullable UseCaseConfig<?> extendedConfig, @Nullable UseCaseConfig<?> cameraConfig)826 public final void bindToCamera(@NonNull CameraInternal camera, 827 @Nullable CameraInternal secondaryCamera, 828 @Nullable UseCaseConfig<?> extendedConfig, 829 @Nullable UseCaseConfig<?> cameraConfig) { 830 synchronized (mCameraLock) { 831 mCamera = camera; 832 mSecondaryCamera = secondaryCamera; 833 addStateChangeCallback(camera); 834 if (secondaryCamera != null) { 835 addStateChangeCallback(secondaryCamera); 836 } 837 } 838 839 mExtendedConfig = extendedConfig; 840 mCameraConfig = cameraConfig; 841 mCurrentConfig = mergeConfigs(camera.getCameraInfoInternal(), mExtendedConfig, 842 mCameraConfig); 843 onBind(); 844 } 845 846 /** 847 * Called when use case is binding to a camera. 848 * 849 * <p>Subclasses can override this callback function to create the necessary objects to 850 * make the use case work correctly. 851 * 852 * <p>After this function is invoked, CameraX will also provide the selected resolution 853 * information to subclasses via {@link #onSuggestedStreamSpecUpdated}. Subclasses should 854 * override it to set up the pipeline according to the selected resolution, so that UseCase 855 * becomes ready to receive data from the camera. 856 * 857 */ 858 @RestrictTo(Scope.LIBRARY_GROUP) onBind()859 public void onBind() { 860 } 861 862 /** 863 * Unbinds use case from a camera. 864 * 865 * <p>The use case de-associates from the camera. Before this function is invoked, the use 866 * case must have been detached from the camera. So that the {@link CameraInternal} 867 * implementation can remove the related resource (e.g. surface) from the working capture 868 * session. Then, when this function is invoked, the use case can also clear all objects and 869 * settings to initial state like it is never bound to a camera. 870 * 871 * <p>After this function is invoked, calling {@link #getCameraControl()} returns a fake no-op 872 * {@link CameraControlInternal} implementation. 873 * 874 */ 875 @RestrictTo(Scope.LIBRARY) unbindFromCamera(@onNull CameraInternal camera)876 public final void unbindFromCamera(@NonNull CameraInternal camera) { 877 // Do any cleanup required by the UseCase implementation 878 onUnbind(); 879 880 synchronized (mCameraLock) { 881 if (camera == mCamera) { 882 removeStateChangeCallback(mCamera); 883 mCamera = null; 884 } 885 886 if (camera == mSecondaryCamera) { 887 removeStateChangeCallback(mSecondaryCamera); 888 mSecondaryCamera = null; 889 } 890 } 891 892 mAttachedStreamSpec = null; 893 mViewPortCropRect = null; 894 895 // Resets the mUseCaseConfig to the initial status when the use case was created to make 896 // the use case reusable. 897 mCurrentConfig = mUseCaseConfig; 898 mExtendedConfig = null; 899 mCameraConfig = null; 900 } 901 902 /** 903 * Called when use case is unbinding from a camera. 904 * 905 * <p>Subclasses can override this callback function to clear the objects created for 906 * their specific purposes. 907 * 908 */ 909 @RestrictTo(Scope.LIBRARY_GROUP) onUnbind()910 public void onUnbind() { 911 } 912 913 /** 914 * Called when use case is attached to the camera. This method is called on main thread. 915 * 916 * <p>Once this function is invoked, the use case is attached to the {@link CameraInternal} 917 * implementation of the associated camera. CameraX starts to open the camera and capture 918 * session with the use case session config. The use case can receive the frame data from the 919 * camera after the capture session is configured. 920 * 921 */ 922 @RestrictTo(Scope.LIBRARY_GROUP) 923 @CallSuper onStateAttached()924 public void onStateAttached() { 925 } 926 927 /** 928 * Called when use case is detached from the camera. This method is called on main thread. 929 * 930 * <p>Once this function is invoked, the use case is detached from the {@link CameraInternal} 931 * implementation of the associated camera. The use case no longer receives frame data from 932 * the camera. 933 * 934 */ 935 @RestrictTo(Scope.LIBRARY_GROUP) onStateDetached()936 public void onStateDetached() { 937 } 938 939 /** 940 * Retrieves a previously attached {@link CameraControlInternal}. 941 * 942 */ 943 @RestrictTo(Scope.LIBRARY_GROUP) getCameraControl()944 protected @NonNull CameraControlInternal getCameraControl() { 945 synchronized (mCameraLock) { 946 if (mCamera == null) { 947 return CameraControlInternal.DEFAULT_EMPTY_INSTANCE; 948 } 949 return mCamera.getCameraControlInternal(); 950 } 951 } 952 953 /** 954 * Sets the view port crop rect calculated at the time of binding. 955 * 956 */ 957 @RestrictTo(Scope.LIBRARY_GROUP) 958 @CallSuper setViewPortCropRect(@onNull Rect viewPortCropRect)959 public void setViewPortCropRect(@NonNull Rect viewPortCropRect) { 960 mViewPortCropRect = viewPortCropRect; 961 } 962 963 /** 964 * Sets the {@link CameraEffect} associated with this use case. 965 * 966 * @throws IllegalArgumentException if the effect targets are not supported by this use case. 967 */ 968 @RestrictTo(Scope.LIBRARY_GROUP) setEffect(@ullable CameraEffect effect)969 public void setEffect(@Nullable CameraEffect effect) { 970 checkArgument(effect == null || isEffectTargetsSupported(effect.getTargets())); 971 mEffect = effect; 972 } 973 974 /** 975 * Gets the {@link CameraEffect} associated with this use case. 976 * 977 */ 978 @RestrictTo(Scope.LIBRARY_GROUP) getEffect()979 public @Nullable CameraEffect getEffect() { 980 return mEffect; 981 } 982 983 /** 984 * Gets the view port crop rect. 985 * 986 */ 987 @RestrictTo(Scope.LIBRARY_GROUP) getViewPortCropRect()988 public @Nullable Rect getViewPortCropRect() { 989 return mViewPortCropRect; 990 } 991 992 /** 993 * Sets the sensor to image buffer transform matrix. 994 * 995 */ 996 @RestrictTo(Scope.LIBRARY_GROUP) 997 @CallSuper setSensorToBufferTransformMatrix(@onNull Matrix sensorToBufferTransformMatrix)998 public void setSensorToBufferTransformMatrix(@NonNull Matrix sensorToBufferTransformMatrix) { 999 mSensorToBufferTransformMatrix = new Matrix(sensorToBufferTransformMatrix); 1000 } 1001 1002 /** 1003 * Gets the sensor to image buffer transform matrix. 1004 * 1005 */ 1006 @RestrictTo(Scope.LIBRARY_GROUP) getSensorToBufferTransformMatrix()1007 public @NonNull Matrix getSensorToBufferTransformMatrix() { 1008 return mSensorToBufferTransformMatrix; 1009 } 1010 1011 /** 1012 * Get image format for the use case. 1013 * 1014 * @return image format for the use case 1015 */ 1016 @RestrictTo(Scope.LIBRARY_GROUP) getImageFormat()1017 public int getImageFormat() { 1018 return mCurrentConfig.getInputFormat(); 1019 } 1020 1021 /** 1022 * Returns a new {@link ResolutionInfo} according to the latest settings of the use case, or 1023 * null if the use case is not bound yet. 1024 * 1025 * <p>This allows the subclasses to return different {@link ResolutionInfo} according to its 1026 * different design. 1027 * 1028 */ 1029 @RestrictTo(Scope.LIBRARY_GROUP) getResolutionInfoInternal()1030 protected @Nullable ResolutionInfo getResolutionInfoInternal() { 1031 CameraInternal camera = getCamera(); 1032 Size resolution = getAttachedSurfaceResolution(); 1033 1034 if (camera == null || resolution == null) { 1035 return null; 1036 } 1037 1038 Rect cropRect = getViewPortCropRect(); 1039 1040 if (cropRect == null) { 1041 cropRect = new Rect(0, 0, resolution.getWidth(), resolution.getHeight()); 1042 } 1043 1044 int rotationDegrees = getRelativeRotation(camera); 1045 1046 return new ResolutionInfo(resolution, cropRect, rotationDegrees); 1047 } 1048 1049 /** 1050 * A set of {@link CameraEffect.Targets} bitmasks supported by the {@link UseCase}. 1051 * 1052 * <p>To apply the {@link CameraEffect} on the {@link UseCase} or one of its ancestors, 1053 * {@link CameraEffect#getTargets()} must be a superset of at least one of the bitmask. For 1054 * example: 1055 * <ul> 1056 * <li>For {@link Preview}, the set only contains [PREVIEW]. {@link Preview} and its ancestors 1057 * supports effects that are supersets of [PREVIEW]: PREVIEW, PREVIEW|VIDEO_CAPTURE, or 1058 * PREVIEW|VIDEO_CAPTURE|IMAGE_CAPTURE. A {@link CameraEffect} that does not target PREVIEW 1059 * cannot be applied to {@link Preview} or its ancestors. 1060 * <li>For {@link StreamSharing}, the set contains [PREVIEW|VIDEO_CAPTURE]. 1061 * {@link StreamSharing} supports effects with targets PREVIEW|VIDEO_CAPTURE or 1062 * PREVIEW|VIDEO_CAPTURE|IMAGE_CAPTURE. 1063 * </ul> 1064 * 1065 * <p>The method returns an empty set if this {@link UseCase} does not support effects. By 1066 * default, this method returns an empty set. 1067 * 1068 */ 1069 @RestrictTo(Scope.LIBRARY_GROUP) getSupportedEffectTargets()1070 protected @NonNull Set<Integer> getSupportedEffectTargets() { 1071 return Collections.emptySet(); 1072 } 1073 1074 /** 1075 * Returns whether the targets can be applied to this {@link UseCase} or one of its ancestors. 1076 * 1077 * @see #getSupportedEffectTargets() 1078 */ 1079 @RestrictTo(Scope.LIBRARY_GROUP) isEffectTargetsSupported(@ameraEffect.Targets int effectTargets)1080 public boolean isEffectTargetsSupported(@CameraEffect.Targets int effectTargets) { 1081 for (Integer useCaseTargets : getSupportedEffectTargets()) { 1082 if (isSuperset(effectTargets, useCaseTargets)) { 1083 return true; 1084 } 1085 } 1086 return false; 1087 } 1088 1089 /** 1090 * Applies the AE fps range to the session config builder according to the stream spec and 1091 * quirk values. 1092 */ 1093 @RestrictTo(Scope.LIBRARY_GROUP) applyExpectedFrameRateRange(SessionConfig.@onNull Builder sessionConfigBuilder, @NonNull StreamSpec streamSpec)1094 protected void applyExpectedFrameRateRange(SessionConfig.@NonNull Builder sessionConfigBuilder, 1095 @NonNull StreamSpec streamSpec) { 1096 // Directly applies the apps' setting if the value is not FRAME_RATE_RANGE_UNSPECIFIED 1097 if (!FRAME_RATE_RANGE_UNSPECIFIED.equals(streamSpec.getExpectedFrameRateRange())) { 1098 sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange()); 1099 return; 1100 } 1101 1102 synchronized (mCameraLock) { 1103 CameraInfoInternal cameraInfoInternal = Preconditions.checkNotNull( 1104 mCamera).getCameraInfoInternal(); 1105 List<AeFpsRangeQuirk> aeFpsRangeQuirks = cameraInfoInternal.getCameraQuirks().getAll( 1106 AeFpsRangeQuirk.class); 1107 Preconditions.checkArgument(aeFpsRangeQuirks.size() <= 1, 1108 "There should not have more than one AeFpsRangeQuirk."); 1109 1110 if (!aeFpsRangeQuirks.isEmpty()) { 1111 sessionConfigBuilder.setExpectedFrameRateRange( 1112 aeFpsRangeQuirks.get(0).getTargetAeFpsRange()); 1113 } 1114 } 1115 } 1116 1117 enum State { 1118 /** Currently waiting for image data. */ 1119 ACTIVE, 1120 /** Currently not waiting for image data. */ 1121 INACTIVE 1122 } 1123 1124 /** 1125 * Callback for when a {@link UseCase} transitions between active/inactive states. 1126 * 1127 */ 1128 @RestrictTo(Scope.LIBRARY_GROUP) 1129 public interface StateChangeCallback { 1130 /** 1131 * Called when a {@link UseCase} becomes active. 1132 * 1133 * <p>When a UseCase is active it expects that all data producers attached to itself 1134 * should start producing data for it to consume. In addition the UseCase will start 1135 * producing data that other classes can be consumed. 1136 */ onUseCaseActive(@onNull UseCase useCase)1137 void onUseCaseActive(@NonNull UseCase useCase); 1138 1139 /** 1140 * Called when a {@link UseCase} becomes inactive. 1141 * 1142 * <p>When a UseCase is inactive it no longer expects data to be produced for it. In 1143 * addition the UseCase will stop producing data for other classes to consume. 1144 */ onUseCaseInactive(@onNull UseCase useCase)1145 void onUseCaseInactive(@NonNull UseCase useCase); 1146 1147 /** 1148 * Called when a {@link UseCase} has updated settings. 1149 * 1150 * <p>When a {@link UseCase} has updated settings, it is expected that the listener will 1151 * use these updated settings to reconfigure the listener's own state. A settings update is 1152 * orthogonal to the active/inactive state change. 1153 */ onUseCaseUpdated(@onNull UseCase useCase)1154 void onUseCaseUpdated(@NonNull UseCase useCase); 1155 1156 /** 1157 * Called when a {@link UseCase} has updated settings that require complete reset of the 1158 * camera. 1159 * 1160 * <p>Updating certain parameters of the use case require a full reset of the camera. This 1161 * includes updating the {@link Surface} used by the use case. 1162 */ onUseCaseReset(@onNull UseCase useCase)1163 void onUseCaseReset(@NonNull UseCase useCase); 1164 } 1165 } 1166