CameraSelector.java

/*
 * Copyright 2019 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;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.LensFacingCameraFilter;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;

/**
 * A set of requirements and priorities used to select a camera or return a filtered set of
 * cameras.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class CameraSelector {

    /** A camera on the device facing the same direction as the device's screen. */
    public static final int LENS_FACING_FRONT = 0;
    /** A camera on the device facing the opposite direction as the device's screen. */
    public static final int LENS_FACING_BACK = 1;

    /** A static {@link CameraSelector} that selects the default front facing camera. */
    @NonNull
    public static final CameraSelector DEFAULT_FRONT_CAMERA =
            new CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build();
    /** A static {@link CameraSelector} that selects the default back facing camera. */
    @NonNull
    public static final CameraSelector DEFAULT_BACK_CAMERA =
            new CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build();

    private LinkedHashSet<CameraFilter> mCameraFilterSet;

    CameraSelector(LinkedHashSet<CameraFilter> cameraFilterSet) {
        mCameraFilterSet = cameraFilterSet;
    }

    /**
     * Selects the first camera that filtered by the {@link CameraFilter}s assigned to this
     * {@link CameraSelector}.
     *
     * <p>When filtering with {@link CameraFilter}, the output set must be contained in the input
     * set, otherwise an IllegalArgumentException will be thrown.
     *
     * @param cameras The camera set being filtered.
     * @return The first camera filtered.
     * @throws IllegalArgumentException If there's no available camera after filtering or the
     *                                  filtered cameras aren't contained in the input set.
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public CameraInternal select(@NonNull LinkedHashSet<CameraInternal> cameras) {
        Iterator<CameraInternal> cameraInternalIterator = filter(cameras).iterator();
        if (cameraInternalIterator.hasNext()) {
            return cameraInternalIterator.next();
        } else {
            throw new IllegalArgumentException("No available camera can be found");
        }
    }

    /**
     * Filters the input {@link CameraInfo}s using the {@link CameraFilter}s assigned to the
     * selector.
     *
     * <p>If the {@link CameraFilter}s assigned to this selector produce a camera info that
     * is not part of the input list, the output list will be empty.
     *
     * <p>An example use case for using this function is when you want to get all
     * {@link CameraInfo}s for all available back facing cameras.
     * <pre>
     * eg.
     * {@code
     * CameraInfo defaultBackCameraInfo = null;
     * CameraSelector selector = new CameraSelector.Builder()
     *      .requireLensFacing(LENS_FACING_BACK).build();
     * List<CameraInfo> cameraInfos = selector.filter(cameraProvider.getAvailableCameraInfos());
     * }
     * </pre>
     *
     * @param cameraInfos The camera infos list being filtered.
     * @return The remaining list of camera infos.
     * @throws UnsupportedOperationException If the {@link CameraFilter}s assigned to the selector
     *                                       try to modify the input camera infos list.
     */
    @NonNull
    public List<CameraInfo> filter(@NonNull List<CameraInfo> cameraInfos) {
        List<CameraInfo> output = new ArrayList<>(cameraInfos);
        for (CameraFilter filter : mCameraFilterSet) {
            output = filter.filter(Collections.unmodifiableList(output));
        }

        output.retainAll(cameraInfos);
        return output;
    }

    /**
     * Filters the input cameras using the {@link CameraFilter} assigned to the selector.
     *
     * <p>The cameras filtered must be contained in the input set. Otherwise it will throw an
     * exception.
     *
     * @param cameras The camera set being filtered.
     * @return The remaining set of cameras.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public LinkedHashSet<CameraInternal> filter(@NonNull LinkedHashSet<CameraInternal> cameras) {
        List<CameraInfo> input = new ArrayList<>();
        for (CameraInternal camera : cameras) {
            input.add(camera.getCameraInfo());
        }

        List<CameraInfo> result = filter(input);

        LinkedHashSet<CameraInternal> output = new LinkedHashSet<>();
        for (CameraInternal camera : cameras) {
            if (result.contains(camera.getCameraInfo())) {
                output.add(camera);
            }
        }

        return output;
    }

    /**
     * Gets the set of {@link CameraFilter} assigned to this camera selector.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    public LinkedHashSet<CameraFilter> getCameraFilterSet() {
        return mCameraFilterSet;
    }

    /**
     * Returns a single lens facing from this camera selector, or null if lens facing has not
     * been set.
     *
     * @return The lens facing.
     * @throws IllegalStateException if a single lens facing cannot be resolved, such as if
     *                               multiple conflicting lens facing requirements exist in this
     *                               camera selector.
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Nullable
    public Integer getLensFacing() {
        Integer currentLensFacing = null;
        for (CameraFilter filter : mCameraFilterSet) {
            if (filter instanceof LensFacingCameraFilter) {
                Integer newLensFacing = ((LensFacingCameraFilter) filter).getLensFacing();
                if (currentLensFacing == null) {
                    currentLensFacing = newLensFacing;
                } else if (!currentLensFacing.equals(newLensFacing)) {
                    // TODO(b/122975195): Now we assume the lens facing of a camera is either
                    //  FRONT or BACK, so if there's conflicting lens facings set, throws an
                    //  exception. It needs to be revisited if we have a third lens facing enum
                    //  in the future.
                    throw new IllegalStateException(
                            "Multiple conflicting lens facing requirements exist.");
                }
            }
        }

        return currentLensFacing;
    }

    /** Builder for a {@link CameraSelector}. */
    public static final class Builder {
        private final LinkedHashSet<CameraFilter> mCameraFilterSet;

        public Builder() {
            mCameraFilterSet = new LinkedHashSet<>();
        }

        private Builder(@NonNull LinkedHashSet<CameraFilter> cameraFilterSet) {
            mCameraFilterSet = new LinkedHashSet<>(cameraFilterSet);
        }

        /**
         * Requires a camera with the specified lens facing.
         *
         * <p>Valid values for lens facing are {@link CameraSelector#LENS_FACING_FRONT} and
         * {@link CameraSelector#LENS_FACING_BACK}.
         *
         * <p>If lens facing is already set, this will add extra requirement for lens facing
         * instead of replacing the previous setting.
         */
        @NonNull
        public Builder requireLensFacing(@LensFacing int lensFacing) {
            mCameraFilterSet.add(new LensFacingCameraFilter(lensFacing));
            return this;
        }

        /**
         * Adds a {@link CameraFilter} to the current set of filters. It can be used to select a
         * specific camera based on customized criteria like Camera2 characteristics.
         *
         * <p>Multiple filters can be added. All filters will be applied by the order they were
         * added when the {@link CameraSelector} is used, and the first camera output from the
         * filters will be selected.
         */
        @NonNull
        public Builder addCameraFilter(@NonNull CameraFilter cameraFilter) {
            mCameraFilterSet.add(cameraFilter);
            return this;
        }

        /**
         * Generates a Builder from another CameraSelector object.
         *
         * @param cameraSelector An existing CameraSelector.
         * @return The new Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static Builder fromSelector(@NonNull CameraSelector cameraSelector) {
            CameraSelector.Builder builder = new CameraSelector.Builder(
                    cameraSelector.getCameraFilterSet());
            return builder;
        }

        /** Builds the {@link CameraSelector}. */
        @NonNull
        public CameraSelector build() {
            return new CameraSelector(mCameraFilterSet);
        }
    }

    /**
     * The direction the camera faces relative to device screen.
     *
     * @hide
     */
    @IntDef({LENS_FACING_FRONT, LENS_FACING_BACK})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @interface LensFacing {
    }
}