1 /* 2 * Copyright 2023 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 androidx.annotation.IntDef; 20 import androidx.annotation.IntRange; 21 import androidx.annotation.RestrictTo; 22 import androidx.camera.core.impl.CameraProviderInitRetryPolicy; 23 import androidx.camera.core.impl.RetryPolicyInternal; 24 import androidx.camera.core.impl.TimeoutRetryPolicy; 25 import androidx.core.util.Preconditions; 26 27 import org.jspecify.annotations.NonNull; 28 import org.jspecify.annotations.Nullable; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 33 /** 34 * Defines a strategy for retrying upon initialization failures of the {@link CameraProvider}. 35 * When the initialization task is interrupted by an error or exception during execution, the 36 * task will determine whether to be rescheduled based on the specified {@link RetryPolicy}. 37 * 38 * <p>Several predefined retry policies are available: 39 * <ul> 40 * <li>{@link RetryPolicy#NEVER}: Never retries the initialization.</li> 41 * <li>{@link RetryPolicy#DEFAULT}: The default retry policy, which retries initialization up to 42 * a maximum timeout of {@link #getDefaultRetryTimeoutInMillis()}, providing a general-purpose 43 * approach for handling most errors.</li> 44 * <li>{@link RetryPolicy#RETRY_UNAVAILABLE_CAMERA}: The retry policy automatically retries upon 45 * encountering errors like the {@link #DEFAULT} policy, and specifically designed to handle 46 * potential device inconsistencies in reporting available camera instances. In cases where the 47 * initial camera configuration fails due to the device underreporting (i.e., not accurately 48 * disclosing all available camera instances) the policy proactively triggers a 49 * reinitialization attempt. If the cameras are not successfully configured within 50 * {@link #getDefaultRetryTimeoutInMillis()}, the initialization is considered a failure.</li> 51 * </ul> 52 * <p>If no {@link RetryPolicy} is specified, {@link RetryPolicy#DEFAULT} will be used as 53 * the default. 54 * 55 * <p>Examples of configuring the {@link RetryPolicy}: 56 * <pre>{@code 57 * // Configuring {@link RetryPolicy#RETRY_UNAVAILABLE_CAMERA} to the CameraXConfig 58 * ProcessCameraProvider.configureInstance( 59 * CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) 60 * .setCameraProviderInitRetryPolicy(RetryPolicy.RETRY_UNAVAILABLE_CAMERA) 61 * .build() 62 * ); 63 * ... 64 * }</pre> 65 * 66 * <p>Configuring a customized {@link RetryPolicy}: 67 * <pre>{@code 68 * ProcessCameraProvider.configureInstance(CameraXConfig.Builder.fromConfig( 69 * Camera2Config.defaultConfig()).setCameraProviderInitRetryPolicy( 70 * executionState -> { 71 * if (executionState.getExecutedTimeInMillis() > 10000L 72 * || executionState.getNumOfAttempts() > 10 73 * || executionState.getStatus() == ExecutionState.STATUS_CONFIGURATION_FAIL) { 74 * return RetryConfig.NOT_RETRY; 75 * } else if (executionState.getStatus() == ExecutionState.STATUS_CAMERA_UNAVAILABLE) { 76 * return RetryConfig.DEFAULT_DELAY_RETRY; 77 * } else { 78 * Log.d("CameraX", "Unknown error occur: " + executionState.getCause()); 79 * return RetryConfig.MINI_DELAY_RETRY; 80 * } 81 * }).build()); 82 * ... 83 * }</pre> 84 * In the second example, the custom retry policy retries the initialization up to 10 times or 85 * for a maximum of 10 seconds. If an unknown error occurs, the retry policy delays the next 86 * retry after a delay defined by {@link RetryConfig#MINI_DELAY_RETRY}. The retry process 87 * stops if the status is {@link ExecutionState#STATUS_CONFIGURATION_FAIL}. For 88 * {@link ExecutionState#STATUS_CAMERA_UNAVAILABLE}, the retry policy applies 89 * {@link RetryConfig#DEFAULT_DELAY_RETRY}. 90 */ 91 @ExperimentalRetryPolicy 92 public interface RetryPolicy { 93 94 /** 95 * Default timeout in milliseconds of the initialization. 96 */ 97 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 98 long DEFAULT_RETRY_TIMEOUT_IN_MILLIS = 6000L; 99 100 /** 101 * A retry policy that prevents any retry attempts and 102 * immediately halts the initialization upon encountering an error. 103 */ 104 @NonNull RetryPolicy NEVER = executionState -> RetryConfig.NOT_RETRY; 105 106 /** 107 * This retry policy increases initialization success by automatically retrying upon 108 * encountering errors. 109 * 110 * <p>By default, it continues retrying for a maximum of 111 * {@link #getDefaultRetryTimeoutInMillis()} milliseconds. To adjust the timeout duration to 112 * specific requirements, utilize {@link RetryPolicy.Builder}. 113 * 114 * <p>Example: Create a policy based on {@link #DEFAULT} with a 10-second timeout: 115 * <pre>{@code 116 * RetryPolicy customTimeoutPolicy = 117 * new RetryPolicy.Builder(RetryPolicy.DEFAULT).setTimeoutInMillis(10000L).build(); 118 * }</pre> 119 */ 120 @NonNull RetryPolicy DEFAULT = new CameraProviderInitRetryPolicy.Legacy( 121 getDefaultRetryTimeoutInMillis()); 122 123 /** 124 * This retry policy automatically retries upon encountering errors and specifically 125 * designed to handle potential device inconsistencies in reporting available camera 126 * instances. If the initial camera configuration fails due to underreporting, the policy 127 * automatically attempts reinitialization. 128 * <p>By default, it perseveres for a maximum of {@link #getDefaultRetryTimeoutInMillis()} 129 * milliseconds before conceding initialization as unsuccessful. For finer control over the 130 * timeout duration, utilize {@link RetryPolicy.Builder}. 131 * <p>Example: Create a policy based on {@link #RETRY_UNAVAILABLE_CAMERA} with a 10-second 132 * timeout: 133 * <pre>{@code 134 * RetryPolicy customTimeoutPolicy = new RetryPolicy.Builder( 135 * RetryPolicy.RETRY_UNAVAILABLE_CAMERA).setTimeoutInMillis(10000L).build(); 136 * }</pre> 137 */ 138 @NonNull RetryPolicy RETRY_UNAVAILABLE_CAMERA = 139 new CameraProviderInitRetryPolicy(getDefaultRetryTimeoutInMillis()); 140 141 /** 142 * Retrieves the default timeout value, in milliseconds, used for initialization retries. 143 * 144 * @return The default timeout duration, expressed in milliseconds. This value determines the 145 * maximum time to wait for a successful initialization before considering the process as 146 * failed. 147 */ getDefaultRetryTimeoutInMillis()148 static long getDefaultRetryTimeoutInMillis() { 149 return DEFAULT_RETRY_TIMEOUT_IN_MILLIS; 150 } 151 152 /** 153 * Called to request a decision on whether to retry the initialization process. 154 * 155 * @param executionState Information about the current execution state of the camera 156 * initialization. 157 * @return A RetryConfig indicating whether to retry, along with any associated delay. 158 */ onRetryDecisionRequested(@onNull ExecutionState executionState)159 @NonNull RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState); 160 161 /** 162 * Returns the maximum allowed retry duration in milliseconds. Initialization will 163 * be terminated if retries take longer than this timeout. A value of 0 indicates 164 * no timeout is enforced. 165 * 166 * <p>The default value is 0 (no timeout). 167 * 168 * @return The retry timeout in milliseconds. 169 */ getTimeoutInMillis()170 default long getTimeoutInMillis() { 171 return 0; 172 } 173 174 /** 175 * A builder class for customizing RetryPolicy behavior. 176 * 177 * <p>Use the {@link #Builder(RetryPolicy)} to modify existing RetryPolicy instances, 178 * typically starting with the predefined options like {@link RetryPolicy#DEFAULT} or 179 * {@link RetryPolicy#RETRY_UNAVAILABLE_CAMERA}. The most common use is to set a custom timeout. 180 * 181 * <p>Example: Create a policy based on {@link RetryPolicy#DEFAULT} with a 10-second timeout: 182 * <pre>{@code 183 * new RetryPolicy.Builder(RetryPolicy.DEFAULT).setTimeoutInMillis(10000L).build() 184 * }</pre> 185 */ 186 @ExperimentalRetryPolicy 187 final class Builder { 188 189 private final RetryPolicy mBasePolicy; 190 private long mTimeoutInMillis; 191 192 /** 193 * Creates a builder based on an existing {@link RetryPolicy}. 194 * 195 * <p>This allows you to start with a predefined policy and add further customizations. 196 * For example, set a timeout to prevent retries from continuing endlessly. 197 * 198 * @param basePolicy The RetryPolicy to use as a starting point. 199 */ Builder(@onNull RetryPolicy basePolicy)200 public Builder(@NonNull RetryPolicy basePolicy) { 201 mBasePolicy = basePolicy; 202 mTimeoutInMillis = basePolicy.getTimeoutInMillis(); 203 } 204 205 /** 206 * Sets a timeout in milliseconds. If retries exceed this duration, they will be 207 * terminated with {@link RetryConfig#NOT_RETRY}. 208 * 209 * @param timeoutInMillis The maximum duration for retries in milliseconds. A value of 0 210 * indicates no timeout. 211 * @return {@code this} for method chaining. 212 */ setTimeoutInMillis(long timeoutInMillis)213 public @NonNull Builder setTimeoutInMillis(long timeoutInMillis) { 214 mTimeoutInMillis = timeoutInMillis; 215 return this; 216 } 217 218 /** 219 * Creates the customized {@link RetryPolicy} instance. 220 * 221 * @return The new {@link RetryPolicy}. 222 */ build()223 public @NonNull RetryPolicy build() { 224 if (mBasePolicy instanceof RetryPolicyInternal) { 225 return ((RetryPolicyInternal) mBasePolicy).copy(mTimeoutInMillis); 226 } 227 return new TimeoutRetryPolicy(mTimeoutInMillis, mBasePolicy); 228 } 229 } 230 231 /** 232 * Provides insights into the current execution state of the camera initialization process. 233 * 234 * <p>The ExecutionState empowers app developers to make informed decisions about retry 235 * strategies and fine-tune timeout settings. It also facilitates comprehensive app-level 236 * logging for tracking initialization success/failure rates and overall camera performance. 237 * 238 * <p>Key use cases: 239 * <ul> 240 * <li>Determining whether to retry initialization based on specific error conditions.</li> 241 * <li>Logging detailed execution information for debugging and analysis.</li> 242 * <li>Monitoring retry success/failure rates to identify potential issues.</li> 243 * </ul> 244 */ 245 @ExperimentalRetryPolicy 246 interface ExecutionState { 247 248 /** 249 * Defines the status codes for the {@link ExecutionState}. 250 */ 251 @IntDef({STATUS_UNKNOWN_ERROR, STATUS_CONFIGURATION_FAIL, STATUS_CAMERA_UNAVAILABLE}) 252 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 253 @Retention(RetentionPolicy.SOURCE) 254 @interface Status { 255 } 256 257 /** 258 * Indicates that the initialization encountered an unknown error. This error may be 259 * transient, and retrying may resolve the issue. 260 */ 261 int STATUS_UNKNOWN_ERROR = 0; 262 263 /** 264 * Indicates that initialization of CameraX failed due to invalid customization options 265 * within the provided {@link CameraXConfig}. This error typically indicates a problem 266 * with the configuration settings and may require developer attention to resolve. 267 * 268 * <p>Possible Causes: 269 * <ul> 270 * <li>Incorrect or incompatible settings within the {@link CameraXConfig}.</li> 271 * <li>Conflicting options that prevent successful initialization.</li> 272 * <li>Missing or invalid values for essential configuration parameters.</li> 273 * </ul> 274 * 275 * <p>To troubleshoot: 276 * Please see {@code getCause().getMessage()} for details, and review configuration of 277 * the {@link CameraXConfig} to identify potential errors or inconsistencies in the 278 * customization options. 279 * 280 * @see androidx.camera.lifecycle.ProcessCameraProvider#configureInstance(CameraXConfig) 281 * @see CameraXConfig.Builder 282 */ 283 int STATUS_CONFIGURATION_FAIL = 1; 284 285 /** 286 * Indicates that the {@link CameraProvider} failed to initialize due to an unavailable 287 * camera. 288 * This error suggests a temporary issue with the device's cameras, and retrying may 289 * resolve the issue. 290 */ 291 int STATUS_CAMERA_UNAVAILABLE = 2; 292 293 /** 294 * Retrieves the status of the most recently completed initialization attempt. 295 * 296 * @return The status code representing the outcome of the initialization. 297 */ 298 @Status getStatus()299 int getStatus(); 300 301 /** 302 * Gets the cause that occurred during the task execution, if any. 303 * 304 * @return The cause that occurred during the task execution, or null if there was no error. 305 */ getCause()306 @Nullable Throwable getCause(); 307 308 /** 309 * Gets the total execution time of the initialization task in milliseconds. 310 * 311 * @return The total execution time of the initialization task in milliseconds. 312 */ getExecutedTimeInMillis()313 long getExecutedTimeInMillis(); 314 315 /** 316 * Indicates the total number of attempts made to initialize the camera, including the 317 * current attempt. The count starts from 1. 318 * 319 * @return The total number of attempts made to initialize the camera. 320 */ getNumOfAttempts()321 int getNumOfAttempts(); 322 } 323 324 /** 325 * Represents the outcome of a {@link RetryPolicy} decision. 326 */ 327 @ExperimentalRetryPolicy 328 final class RetryConfig { 329 330 private static final long MINI_DELAY_MILLIS = 100L; 331 private static final long DEFAULT_DELAY_MILLIS = 500L; 332 333 /** A RetryConfig indicating that no further retries should be attempted. */ 334 public static final @NonNull RetryConfig NOT_RETRY = new RetryConfig(false, 0L); 335 336 /** 337 * A RetryConfig indicating that the initialization should be retried after the default 338 * delay (determined by {@link #getDefaultRetryDelayInMillis()}). This delay provides 339 * sufficient time for typical device recovery processes, balancing retry efficiency 340 * and minimizing user wait time. 341 */ 342 public static final @NonNull RetryConfig DEFAULT_DELAY_RETRY = new RetryConfig(true); 343 344 /** 345 * A RetryConfig indicating that the initialization should be retried after a minimum 346 * delay of 100 milliseconds. 347 * 348 * This short delay serves two purposes: 349 * <ul> 350 * <li>Reduced Latency: Minimizes the wait time for the camera to become available, 351 * improving user experience. 352 * <li>Camera Self-Recovery: Provides a brief window for the camera to potentially 353 * recover from temporary issues. 354 * </ul> 355 * This approach balances quick retries with potential self-recovery, aiming for the 356 * fastest possible camera restoration. 357 */ 358 public static final @NonNull RetryConfig MINI_DELAY_RETRY = 359 new RetryConfig(true, MINI_DELAY_MILLIS); 360 361 /** 362 * A RetryConfig indicating that the initialization should be considered complete 363 * without retrying. This config is intended for internal use and is not intended to 364 * trigger further retries. It represents the legacy behavior of not failing the 365 * initialization task for minor issues. 366 */ 367 @RestrictTo(RestrictTo.Scope.LIBRARY) 368 public static @NonNull RetryConfig COMPLETE_WITHOUT_FAILURE = 369 new RetryConfig(false, 0, true); 370 371 /** 372 * Returns the recommended default delay to optimize retry attempts and camera recovery. 373 * 374 * @return The default delay value, carefully calibrated based on extensive lab testing to: 375 * <ul> 376 * <li>Provide sufficient time for typical device recovery processes in common bad 377 * camera states.</li> 378 * <li>Strike an optimal balance between minimizing latency and avoiding excessive retries, 379 * ensuring efficient camera initialization without unnecessary overhead.</li> 380 * </ul> 381 * This value represents the generally recommended delay for most scenarios, striking a 382 * balance between providing adequate time for camera recovery and maintaining a smooth 383 * user experience. 384 */ getDefaultRetryDelayInMillis()385 public static long getDefaultRetryDelayInMillis() { 386 return DEFAULT_DELAY_MILLIS; 387 } 388 389 private final long mDelayInMillis; 390 private final boolean mShouldRetry; 391 private final boolean mCompleteWithoutFailure; 392 RetryConfig(boolean shouldRetry)393 private RetryConfig(boolean shouldRetry) { 394 this(shouldRetry, RetryConfig.getDefaultRetryDelayInMillis()); 395 } 396 RetryConfig(boolean shouldRetry, long delayInMillis)397 private RetryConfig(boolean shouldRetry, long delayInMillis) { 398 this(shouldRetry, delayInMillis, false); 399 } 400 401 /** 402 * Constructor for determining whether to retry the initialization. 403 * 404 * @param shouldRetry Whether to retry the initialization. 405 * @param delayInMillis The delay time in milliseconds before starting the next 406 * retry. 407 * @param completeWithoutFailure Indicates whether to skip retries and not fail the 408 * initialization. This is a flag for legacy behavior to 409 * avoid failing the initialization task for minor issues. 410 * When this flag is set to true, `shouldRetry` must be 411 * false. 412 */ RetryConfig(boolean shouldRetry, long delayInMillis, boolean completeWithoutFailure)413 private RetryConfig(boolean shouldRetry, long delayInMillis, 414 boolean completeWithoutFailure) { 415 mShouldRetry = shouldRetry; 416 mDelayInMillis = delayInMillis; 417 if (completeWithoutFailure) { 418 Preconditions.checkArgument(!shouldRetry, 419 "shouldRetry must be false when completeWithoutFailure is set to true"); 420 } 421 mCompleteWithoutFailure = completeWithoutFailure; 422 } 423 424 /** 425 * Determines whether the initialization should be retried. 426 * 427 * @return true if the initialization should be retried, false otherwise. 428 */ shouldRetry()429 public boolean shouldRetry() { 430 return mShouldRetry; 431 } 432 433 /** 434 * Gets the delay time in milliseconds before the next retry attempt should be made. 435 * 436 * @return The delay time in milliseconds. 437 */ getRetryDelayInMillis()438 public long getRetryDelayInMillis() { 439 return mDelayInMillis; 440 } 441 442 /** 443 * Signals to treat initialization errors as successful for legacy behavior compatibility. 444 * 445 * <p>This config is intended for internal use and is not intended to trigger further 446 * retries. 447 * 448 * @return true if initialization should be deemed complete without additional retries, 449 * despite any errors encountered. Otherwise, returns false. 450 */ 451 @RestrictTo(RestrictTo.Scope.LIBRARY) shouldCompleteWithoutFailure()452 public boolean shouldCompleteWithoutFailure() { 453 return mCompleteWithoutFailure; 454 } 455 456 /** 457 * A builder class for creating and customizing {@link RetryConfig} objects. 458 * 459 * <p>While predefined configs like {@link RetryConfig#DEFAULT_DELAY_RETRY} are 460 * recommended for typical recovery scenarios, this builder allows for fine-tuned control 461 * when specific requirements necessitate a different approach. 462 */ 463 @ExperimentalRetryPolicy 464 public static final class Builder { 465 466 private boolean mShouldRetry = true; 467 private long mTimeoutInMillis = RetryConfig.getDefaultRetryDelayInMillis(); 468 469 /** 470 * Specifies whether a retry should be attempted. 471 * 472 * @param shouldRetry If true (the default), initialization will be retried. 473 * If false, initialization will not be retried. 474 * @return {@code this} for method chaining. 475 */ setShouldRetry(boolean shouldRetry)476 public @NonNull Builder setShouldRetry(boolean shouldRetry) { 477 mShouldRetry = shouldRetry; 478 return this; 479 } 480 481 /** 482 * Sets the retry delay in milliseconds. 483 * 484 * <p>If set, the initialization will be retried after the specified delay. For optimal 485 * results, the delay should be within the range of 100 to 2000 milliseconds. This 486 * aligns with lab testing, which suggests this range provides sufficient recovery 487 * time for most common camera issues while minimizing latency. 488 * 489 * @param timeoutInMillis The delay in milliseconds. 490 * @return {@code this} for method chaining. 491 */ setRetryDelayInMillis( @ntRangefrom = 100, to = 2000) long timeoutInMillis)492 public @NonNull Builder setRetryDelayInMillis( 493 @IntRange(from = 100, to = 2000) long timeoutInMillis) { 494 mTimeoutInMillis = timeoutInMillis; 495 return this; 496 } 497 498 /** 499 * Builds the customized {@link RetryConfig} object. 500 * 501 * @return The configured RetryConfig. 502 */ build()503 public @NonNull RetryConfig build() { 504 return new RetryConfig(mShouldRetry, mTimeoutInMillis); 505 } 506 } 507 } 508 } 509