CameraProviderInitRetryPolicy.java

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.core.impl;

import static androidx.camera.core.impl.CameraValidator.CameraIdListIncorrectException;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.ExperimentalRetryPolicy;
import androidx.camera.core.Logger;
import androidx.camera.core.RetryPolicy;

/**
 * Basic retry policy that automatically retries most failures with a standard delay.
 *
 * <p>This policy will initiate a retry with the
 * {@link RetryConfig#DEFAULT_DELAY_RETRY} delay for any failure status except
 * {@link ExecutionState#STATUS_CONFIGURATION_FAIL}.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@ExperimentalRetryPolicy
public final class CameraProviderInitRetryPolicy implements RetryPolicyInternal {

    private final RetryPolicy mDelegatePolicy;

    public CameraProviderInitRetryPolicy(long timeoutInMillis) {
        mDelegatePolicy = new TimeoutRetryPolicy(timeoutInMillis, new RetryPolicy() {
            @NonNull
            @Override
            public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
                if (executionState.getStatus() == ExecutionState.STATUS_CONFIGURATION_FAIL) {
                    return RetryConfig.NOT_RETRY;
                }

                return RetryConfig.DEFAULT_DELAY_RETRY;
            }

            @Override
            public long getTimeoutInMillis() {
                return timeoutInMillis;
            }
        });
    }

    @NonNull
    @Override
    public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
        return mDelegatePolicy.onRetryDecisionRequested(executionState);
    }

    @Override
    public long getTimeoutInMillis() {
        return mDelegatePolicy.getTimeoutInMillis();
    }

    @NonNull
    @Override
    public RetryPolicy copy(long timeoutInMillis) {
        return new CameraProviderInitRetryPolicy(timeoutInMillis);
    }

    /**
     * A legacy implementation of {@link CameraProviderInitRetryPolicy} that treats
     * {@link CameraIdListIncorrectException} as a special case.
     *
     * <p>In older versions of the CameraProviderInitRetryPolicy, there's a special rule for
     * handling CameraValidator.CameraIdListIncorrectException errors if:
     * <ul>
     *   <li>The camera initialization task takes longer than the allowed timeout.
     *   <li>The error is CameraValidator.CameraIdListIncorrectException.
     *   <li>There's more than one camera available.
     * </ul>
     * Then:
     * <ul>
     *   <li>The task is considered complete and won't be retried.
     * </ul>
     */
    public static final class Legacy implements RetryPolicyInternal {

        private final RetryPolicy mBasePolicy;

        public Legacy(long timeoutInMillis) {
            mBasePolicy = new CameraProviderInitRetryPolicy(timeoutInMillis);
        }

        @NonNull
        @Override
        public RetryConfig onRetryDecisionRequested(@NonNull ExecutionState executionState) {
            if (!mBasePolicy.onRetryDecisionRequested(executionState).shouldRetry()) {
                Throwable cause = executionState.getCause();
                if (cause instanceof CameraIdListIncorrectException) {
                    Logger.e("CameraX", "The device might underreport the amount of the "
                            + "cameras. Finish the initialize task since we are already "
                            + "reaching the maximum number of retries.");
                    if (((CameraIdListIncorrectException) cause).getAvailableCameraCount() > 0) {
                        // If the initialization task execution time exceeds the timeout
                        // threshold and the error type is CameraIdListIncorrectException,
                        // consider the initialization complete without retrying.
                        return RetryConfig.COMPLETE_WITHOUT_FAILURE;
                    }
                }
                return RetryConfig.NOT_RETRY;
            }
            return RetryConfig.DEFAULT_DELAY_RETRY;
        }

        @Override
        public long getTimeoutInMillis() {
            return mBasePolicy.getTimeoutInMillis();
        }

        @NonNull
        @Override
        public RetryPolicy copy(long timeoutInMillis) {
            return new Legacy(timeoutInMillis);
        }
    }
}