Extensions.java

/*
 * Copyright 2020 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.extensions;

import android.content.Context;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.experimental.UseExperimental;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraFilter;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.ExperimentalCameraFilter;
import androidx.camera.core.UseCase;
import androidx.camera.core.impl.CameraFilters;
import androidx.camera.core.impl.ExtendableUseCaseConfigFactory;
import androidx.camera.core.impl.ImageCaptureConfig;
import androidx.camera.core.impl.PreviewConfig;
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.BeautyPreviewExtenderImpl;
import androidx.camera.extensions.impl.BokehImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.BokehPreviewExtenderImpl;
import androidx.camera.extensions.impl.HdrImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.HdrPreviewExtenderImpl;
import androidx.camera.extensions.impl.NightImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
import androidx.camera.extensions.internal.ImageCaptureConfigProvider;
import androidx.camera.extensions.internal.PreviewConfigProvider;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedHashSet;
import java.util.List;

/**
 * A class for querying and controlling the extensions that are enable for individual
 * {@link Camera} instances.
 *
 * <p> The typical usages is to check whether or not a Camera and/or the {@link UseCase}
 * combinations support the extension by using {@link #isExtensionAvailable(Camera, int)} and
 * {@link #checkUseCases(Camera, List, int)}. Then after it has been determined that the
 * extension can be enable then a {@link #setExtension(Camera, int)} call can be used to set the
 * specified extension on the camera.
 *
 * <p> When the Camera has been set to a particular extension it might require the camera to
 * restart which can cause the preview to temporarily stop. Once the extension has been enable
 * for a Camera instance then it will stay in that extension mode until the extension has been
 * disabled. Setting extension modes is separate for each camera instance.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class Extensions {
    private static final String TAG = "Extensions";

    /** Normal mode without any specific effect applied. */
    public static final int EXTENSION_MODE_NONE = 0;
    /** Bokeh mode that is often applied as portrait mode for people pictures. */
    public static final int EXTENSION_MODE_BOKEH = 1;
    /**
     * HDR mode that may get source pictures with different AE settings to generate a best
     * result.
     */
    public static final int EXTENSION_MODE_HDR = 2;
    /**
     * Night mode is used for taking better still capture images under low-light situations,
     * typically at night time.
     */
    public static final int EXTENSION_MODE_NIGHT = 3;
    /**
     * Beauty mode is used for taking still capture images that incorporate facial changes
     * like skin tone, geometry, or retouching.
     */
    public static final int EXTENSION_MODE_BEAUTY = 4;
    /**
     * Auto mode is used for taking still capture images that automatically adjust to the
     * surrounding scenery.
     */
    public static final int EXTENSION_MODE_AUTO = 5;

    private final Context mContext;

    /**
     * The different extension modes that a {@link Camera} can be configured for.
     *
     * <p>Not all devices and camera support the different extension modes. To query whether or
     * not a specific Camera supports an extension mode use
     * {@link Extensions#isExtensionAvailable(Camera, int)}.
     *
     * @hide
     */
    @IntDef({EXTENSION_MODE_NONE, EXTENSION_MODE_BOKEH, EXTENSION_MODE_HDR, EXTENSION_MODE_NIGHT,
            EXTENSION_MODE_BEAUTY, EXTENSION_MODE_AUTO})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @interface ExtensionMode {
    }

    Extensions(@NonNull Context context) {
        mContext = context;
    }

    /**
     * Sets the specified extension mode for the Camera.
     *
     * To return to a non-extensions mode Camera set the extension mode as NONE.
     * For full list of extensions see Extension Modes
     *
     * @param camera The camera that the UseCases are attached to
     * @param mode   The extension mode to set. Setting this to EXTENSION_NONE will
     *               remove the current extension and will always succeed.
     * @throws IllegalArgumentException if the specified extension mode can not be set on the
     *                                  Camera. This can happen if the extension is not supported
     *                                  on this camera. This might also
     *                                  because the combination of UseCases attached to the
     *                                  Camera. Use {@link
     *                                  #checkUseCases(Camera, List, int)} to verify that the
     *                                  Camera can support the list of
     *                                  UseCases for the extension.
     */
    @UseExperimental(markerClass = ExperimentalCameraFilter.class)
    public void setExtension(@NonNull Camera camera, @ExtensionMode int mode) {
        if (!isExtensionAvailable(camera, mode)) {
            throw new IllegalArgumentException("Extension mode not supported on camera: " + mode);
        }

        CameraFilter extensionsFilter = getFilter(mode);
        Camera extensionCamera =
                extensionsFilter.filter(
                        new LinkedHashSet<>(camera.getCameraInternals())).iterator().next();
        CameraInfo extensionsCameraInfo = extensionCamera.getCameraInfo();

        ExtendableUseCaseConfigFactory factory = new ExtendableUseCaseConfigFactory();
        factory.installDefaultProvider(ImageCaptureConfig.class,
                new ImageCaptureConfigProvider(mode, extensionsCameraInfo, mContext));
        factory.installDefaultProvider(PreviewConfig.class,
                new PreviewConfigProvider(mode, extensionsCameraInfo, mContext));

        // Set the Camera
        ExtensionsConfig extensionsConfig =
                new ExtensionsConfig.Builder()
                        .setExtensionMode(mode)
                        .setCameraFilter(getFilter(mode))
                        .setUseCaseConfigFactory(factory)
                        .build();

        // Set the config on the camera
        try {
            camera.setExtendedConfig(extensionsConfig);
        } catch (CameraUseCaseAdapter.CameraException e) {
            throw new IllegalArgumentException("Camera unable to support the extension with the "
                    + "attached UseCases. " + e);
        }
    }

    /**
     * Returns the extension mode that is currently set on the camera.
     */
    public @ExtensionMode int getExtension(@NonNull Camera camera) {
        Object extensionsConfigObject = camera.getExtendedConfig();

        if (extensionsConfigObject instanceof ExtensionsConfig) {
            ExtensionsConfig extensionsConfig = (ExtensionsConfig) extensionsConfigObject;
            return extensionsConfig.getExtensionMode();
        }
        return EXTENSION_MODE_NONE;
    }

    /**
     * Returns true if the particular extension mode is available for the specified
     * Camera.
     *
     * <p> This check is independent of the {@link UseCase} which are currently attached to the
     * {@link Camera}. To check whether the Camera can support the attached UseCases use {@link
     * #checkUseCases(Camera, List, int)}.
     *
     * @param camera The Camera to check if it supports the extension.
     * @param mode   The extension mode to check
     */
    @UseExperimental(markerClass = ExperimentalCameraFilter.class)
    public boolean isExtensionAvailable(@NonNull Camera camera, @ExtensionMode int mode) {
        CameraFilter filter = getFilter(mode);

        // Extension is available for the camera if it contains a CameraInternal which supports
        // the extension
        return !filter.filter(new LinkedHashSet<>(camera.getCameraInternals())).isEmpty();
    }

    @UseExperimental(markerClass = ExperimentalCameraFilter.class)
    private CameraFilter getFilter(@ExtensionMode int mode) {
        CameraFilter filter;
        try {
            switch (mode) {
                case EXTENSION_MODE_BOKEH:
                    filter = new ExtensionCameraFilter(new BokehPreviewExtenderImpl(),
                            new BokehImageCaptureExtenderImpl());
                    break;
                case EXTENSION_MODE_HDR:
                    filter = new ExtensionCameraFilter(new HdrPreviewExtenderImpl(),
                            new HdrImageCaptureExtenderImpl());
                    break;
                case EXTENSION_MODE_NIGHT:
                    filter = new ExtensionCameraFilter(new NightPreviewExtenderImpl(),
                            new NightImageCaptureExtenderImpl());
                    break;
                case EXTENSION_MODE_BEAUTY:
                    filter = new ExtensionCameraFilter(new BeautyPreviewExtenderImpl(),
                            new BeautyImageCaptureExtenderImpl());
                    break;
                case EXTENSION_MODE_AUTO:
                    filter = new ExtensionCameraFilter(new AutoPreviewExtenderImpl(),
                            new AutoImageCaptureExtenderImpl());
                    break;
                case EXTENSION_MODE_NONE:
                default:
                    filter = CameraFilters.ANY;
            }
        } catch (NoClassDefFoundError e) {
            filter = CameraFilters.NONE;
        }

        return filter;
    }

    /**
     * Checks if the list of UseCases attached to the Camera can support the
     * extension.
     *
     * If the list of UseCases exceeds the capacity of Surfaces for the Camera then
     * it returns a list of UseCase lists that can be removed in order to allow for
     * the extension to be enabled. Any of the individual lists can be removed.
     *
     * @return null if the Camera supports the extension using the list of UseCases, otherwise a
     * list of UseCase list to remove.
     */
    @NonNull
    public List<List<UseCase>> checkUseCases(@NonNull Camera camera,
            @NonNull List<UseCase> useCases,
            @ExtensionMode int mode) {
        throw new UnsupportedOperationException("not yet implemented");
    }
}