Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 582ab2d4 authored by Jyoti Bhayana's avatar Jyoti Bhayana Committed by Android (Google) Code Review
Browse files

Merge "Add multi-client support in camera2" into main

parents 5111000e 16adef06
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -5048,15 +5048,27 @@ package android.hardware.biometrics {
package android.hardware.camera2 {
  public final class CameraCharacteristics extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CameraCharacteristics.Key<?>> {
    field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.SharedSessionConfiguration> SHARED_SESSION_CONFIGURATION;
  }
  public abstract class CameraDevice implements java.lang.AutoCloseable {
    method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
    field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
    field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
    field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public static final int SESSION_OPERATION_MODE_SHARED = 2; // 0x2
    field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
  }
  public abstract static class CameraDevice.StateCallback {
    method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public void onClientSharedAccessPriorityChanged(@NonNull android.hardware.camera2.CameraDevice, boolean);
    method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public void onOpenedInSharedMode(@NonNull android.hardware.camera2.CameraDevice, boolean);
  }
  public final class CameraManager {
    method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public boolean isCameraDeviceSharingSupported(@NonNull String) throws android.hardware.camera2.CameraAccessException;
    method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
    method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openSharedCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
  }
  public abstract static class CameraManager.AvailabilityCallback {
@@ -5064,6 +5076,12 @@ package android.hardware.camera2 {
    method @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER) public void onCameraOpened(@NonNull String, @NonNull String);
  }
  @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract class CameraSharedCaptureSession extends android.hardware.camera2.CameraCaptureSession {
    ctor public CameraSharedCaptureSession();
    method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract int startStreaming(@NonNull java.util.List<android.view.Surface>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.CaptureCallback) throws android.hardware.camera2.CameraAccessException;
    method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract void stopStreaming() throws android.hardware.camera2.CameraAccessException;
  }
}
package android.hardware.camera2.extension {
@@ -5172,6 +5190,28 @@ package android.hardware.camera2.params {
    field public static final int ROTATION_90 = 1; // 0x1
  }
  public final class SessionConfiguration implements android.os.Parcelable {
    field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public static final int SESSION_SHARED = 2; // 0x2
  }
  @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public final class SharedSessionConfiguration {
    method @Nullable public android.graphics.ColorSpace getColorSpace();
    method @NonNull public java.util.List<android.hardware.camera2.params.SharedSessionConfiguration.SharedOutputConfiguration> getOutputStreamsInformation();
  }
  public static final class SharedSessionConfiguration.SharedOutputConfiguration {
    method public int getDataspace();
    method public int getFormat();
    method public int getMirrorMode();
    method @Nullable public String getPhysicalCameraId();
    method @NonNull public android.util.Size getSize();
    method public long getStreamUseCase();
    method public int getSurfaceType();
    method public int getTimestampBase();
    method public long getUsage();
    method public boolean isReadoutTimestampEnabled();
  }
}
package android.hardware.contexthub {
+61 −1
Original line number Diff line number Diff line
@@ -19,9 +19,9 @@ package android.hardware.camera2;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.ExtensionKey;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
@@ -6172,6 +6172,66 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION =
            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);

    /**
     * <p>Color space used for shared session configuration for all the output targets
     * when camera is opened in shared mode. This should be one of the values specified in
     * availableColorSpaceProfilesMap.</p>
     * <p><b>Possible values:</b></p>
     * <ul>
     *   <li>{@link #SHARED_SESSION_COLOR_SPACE_UNSPECIFIED UNSPECIFIED}</li>
     *   <li>{@link #SHARED_SESSION_COLOR_SPACE_SRGB SRGB}</li>
     *   <li>{@link #SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 DISPLAY_P3}</li>
     *   <li>{@link #SHARED_SESSION_COLOR_SPACE_BT2020_HLG BT2020_HLG}</li>
     * </ul>
     *
     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
     * @see #SHARED_SESSION_COLOR_SPACE_UNSPECIFIED
     * @see #SHARED_SESSION_COLOR_SPACE_SRGB
     * @see #SHARED_SESSION_COLOR_SPACE_DISPLAY_P3
     * @see #SHARED_SESSION_COLOR_SPACE_BT2020_HLG
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final Key<Integer> SHARED_SESSION_COLOR_SPACE =
            new Key<Integer>("android.sharedSession.colorSpace", int.class);

    /**
     * <p>List of shared output configurations that this camera device supports when
     * camera is opened in shared mode. Array contains following entries for each supported
     * shared configuration:
     * 1) surface type
     * 2) width
     * 3) height
     * 4) format
     * 5) mirrorMode
     * 6) useReadoutTimestamp
     * 7) timestampBase
     * 8) dataspace
     * 9) usage
     * 10) streamUsecase
     * 11) physical camera id len
     * 12) physical camera id as UTF-8 null terminated string.</p>
     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final Key<long[]> SHARED_SESSION_OUTPUT_CONFIGURATIONS =
            new Key<long[]>("android.sharedSession.outputConfigurations", long[].class);

    /**
     * <p>The available stream configurations that this camera device supports for
     * shared capture session when camera is opened in shared mode. Android camera framework
     * will generate this tag if the camera device can be opened in shared mode.</p>
     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
     * @hide
     */
    @SystemApi
    @NonNull
    @SyntheticKey
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final Key<android.hardware.camera2.params.SharedSessionConfiguration> SHARED_SESSION_CONFIGURATION =
            new Key<android.hardware.camera2.params.SharedSessionConfiguration>("android.sharedSession.configuration", android.hardware.camera2.params.SharedSessionConfiguration.class);


    /**
     * Mapping from INFO_SESSION_CONFIGURATION_QUERY_VERSION to session characteristics key.
+54 −1
Original line number Diff line number Diff line
@@ -446,6 +446,17 @@ public abstract class CameraDevice implements AutoCloseable {
    public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
            1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;

     /**
     * Shared camera operation mode.
     *
     * @see #CameraSharedCaptureSession
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final int SESSION_OPERATION_MODE_SHARED =
            2; // ICameraDeviceUser.SHARED_MODE;

    /**
     * First vendor-specific operating mode
     *
@@ -461,6 +472,7 @@ public abstract class CameraDevice implements AutoCloseable {
    @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
            {SESSION_OPERATION_MODE_NORMAL,
             SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
             SESSION_OPERATION_MODE_SHARED,
             SESSION_OPERATION_MODE_VENDOR_START})
    public @interface SessionOperatingMode {};

@@ -1240,7 +1252,6 @@ public abstract class CameraDevice implements AutoCloseable {
     *
     * </ul>
     *
     *
     * @param config A session configuration (see {@link SessionConfiguration}).
     *
     * @throws IllegalArgumentException In case the session configuration is invalid; or the output
@@ -1558,6 +1569,48 @@ public abstract class CameraDevice implements AutoCloseable {
         */
        public abstract void onOpened(@NonNull CameraDevice camera); // Must implement

        /**
         * The method called when a camera device has finished opening in shared mode,
         * where there can be more than one client accessing the same camera.
         *
         * <p>At this point, the camera device is ready to use, and
         * {@link CameraDevice#createCaptureSession} can be called to set up the shared capture
         * session.</p>
         *
         * @param camera the camera device that has become opened
         * @param isPrimaryClient true if the client opening the camera is currently the primary
         *                             client.
         * @hide
         */
        @SystemApi
        @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
        public void onOpenedInSharedMode(@NonNull CameraDevice camera, boolean isPrimaryClient) {
          // Default empty implementation
        }

        /**
         * The method called when client access priorities have changed for a camera device opened
         * in shared mode where there can be more than one client accessing the same camera.
         *
         * If the client priority changed from secondary to primary, then it can now
         * create capture request and change the capture request parameters. If client priority
         * changed from primary to secondary, that implies that a higher priority client has also
         * opened the camera in shared mode and the new client is now a primary client
         *
         * @param camera the camera device whose access priorities have changed.
         * @param isPrimaryClient true if the client is now the primary client.
         *                        false if another higher priority client also opened the
         *                        camera and is now the new primary client and this client is
         *                        now a secondary client.
         * @hide
         */
        @SystemApi
        @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
        public void onClientSharedAccessPriorityChanged(@NonNull CameraDevice camera,
                boolean isPrimaryClient) {
          // Default empty implementation
        }

        /**
         * The method called when a camera device has been closed with
         * {@link CameraDevice#close}.
+123 −33
Original line number Diff line number Diff line
@@ -975,6 +975,46 @@ public final class CameraManager {
        return CameraDeviceSetupImpl.isCameraDeviceSetupSupported(chars);
    }

    /**
     * Checks if a camera device can be opened in a shared mode for a given {@code cameraId}.
     * If this method returns false for a {@code cameraId}, calling {@link #openSharedCamera}
     * for that {@code cameraId} will throw an {@link UnsupportedOperationException}.
     *
     * @param cameraId The unique identifier of the camera device for which sharing support is
     *                 being queried. This identifier must be present in
     *                 {@link #getCameraIdList()}.
     *
     * @return {@code true} if camera can be opened in shared mode
     *                      for the provided {@code cameraId}; {@code false} otherwise.
     *
     * @throws IllegalArgumentException If {@code cameraId} is null, or if {@code cameraId} does not
     *                                  match any device in {@link #getCameraIdList()}.
     * @throws CameraAccessException    if the camera device has been disconnected.
     *
     * @see #getCameraIdList()
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    @SystemApi
    public boolean isCameraDeviceSharingSupported(@NonNull String cameraId)
            throws CameraAccessException {
        if (cameraId == null) {
            throw new IllegalArgumentException("Camera ID was null");
        }

        if (CameraManagerGlobal.sCameraServiceDisabled
                || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(),
                getDevicePolicyFromContext(mContext))).contains(cameraId)) {
            throw new IllegalArgumentException(
                    "Camera ID '" + cameraId + "' not available on device.");
        }

        CameraCharacteristics chars = getCameraCharacteristics(cameraId);
        long[] sharedOutputConfiguration =
                chars.get(CameraCharacteristics.SHARED_SESSION_OUTPUT_CONFIGURATIONS);
        return (sharedOutputConfiguration != null);
    }

    /**
     * Retrieves the AttributionSourceState to pass to the CameraService.
     *
@@ -1036,6 +1076,9 @@ public final class CameraManager {
     * @param cameraId The unique identifier of the camera device to open
     * @param callback The callback for the camera. Must not be null.
     * @param executor The executor to invoke the callback with. Must not be null.
     * @param oomScoreOffset The minimum oom score that cameraservice must see for this client.
     * @param rotationOverride The type of rotation override.
     * @param sharedMode Parameter specifying if the camera should be opened in shared mode.
     *
     * @throws CameraAccessException if the camera is disabled by device policy,
     * too many camera devices are already open, or the cameraId does not match
@@ -1051,7 +1094,8 @@ public final class CameraManager {
     */
    private CameraDevice openCameraDeviceUserAsync(String cameraId,
            CameraDevice.StateCallback callback, Executor executor,
            final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
            final int oomScoreOffset, int rotationOverride, boolean sharedMode)
            throws CameraAccessException {
        CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
        CameraDevice device = null;
        synchronized (mLock) {
@@ -1070,7 +1114,7 @@ public final class CameraManager {
                        characteristics,
                        this,
                        mContext.getApplicationInfo().targetSdkVersion,
                        mContext, cameraDeviceSetup);
                        mContext, cameraDeviceSetup, sharedMode);
            ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();

            try {
@@ -1091,7 +1135,7 @@ public final class CameraManager {
                                mContext.getApplicationInfo().targetSdkVersion,
                                rotationOverride,
                                clientAttribution,
                                getDevicePolicyFromContext(mContext));
                                getDevicePolicyFromContext(mContext), sharedMode);
            } catch (ServiceSpecificException e) {
                if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
                    throw new AssertionError("Should've gone down the shim path");
@@ -1218,7 +1262,8 @@ public final class CameraManager {
            @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
            throws CameraAccessException {

        openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler));
        openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
                /*oomScoreOffset*/0, getRotationOverride(mContext), /*sharedMode*/false);
    }

    /**
@@ -1258,7 +1303,7 @@ public final class CameraManager {
                         /*oomScoreOffset*/0,
                         overrideToPortrait
                                 ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
                                 : ICameraService.ROTATION_OVERRIDE_NONE);
                                 : ICameraService.ROTATION_OVERRIDE_NONE, /*sharedMode*/false);
    }

    /**
@@ -1303,9 +1348,56 @@ public final class CameraManager {
        if (executor == null) {
            throw new IllegalArgumentException("executor was null");
        }
        openCameraImpl(cameraId, callback, executor);
        openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
                getRotationOverride(mContext), /*sharedMode*/false);
    }

    /**
     * Opens a shared connection to a camera with the given ID.
     *
     * <p>The behavior of this method matches that of
     * {@link #openCamera(String, Executor, StateCallback)}, except that it opens the camera in
     * shared mode where more than one client can access the camera at the same time.</p>
     *
     * @param cameraId The unique identifier of the camera device to open.
     * @param executor The executor which will be used when invoking the callback.
     * @param callback The callback which is invoked once the camera is opened
     *
     * @throws CameraAccessException if the camera is disabled by device policy, or is being used
     *                               by a higher-priority client in non-shared mode or the device
     *                               has reached its maximal resource and cannot open this camera
     *                               device.
     *
     * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
     *                                  or the cameraId does not match any currently or previously
     *                                  available camera device.
     *
     * @throws SecurityException if the application does not have permission to
     *                           access the camera
     *
     * @see #getCameraIdList
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = {
            android.Manifest.permission.SYSTEM_CAMERA,
            android.Manifest.permission.CAMERA,
    })
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public void openSharedCamera(@NonNull String cameraId,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull final CameraDevice.StateCallback callback)
            throws CameraAccessException {
        if (executor == null) {
            throw new IllegalArgumentException("executor was null");
        }
        openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
                getRotationOverride(mContext), /*sharedMode*/true);
    }


    /**
     * Open a connection to a camera with the given ID. Also specify what oom score must be offset
     * by cameraserver for this client. This api can be useful for system
@@ -1372,29 +1464,35 @@ public final class CameraManager {
                    "oomScoreOffset < 0, cannot increase priority of camera client");
        }
        openCameraImpl(cameraId, callback, executor, oomScoreOffset,
                getRotationOverride(mContext));
                getRotationOverride(mContext), /*sharedMode*/false);
    }

    /**
     * Open a connection to a camera with the given ID, on behalf of another application.
     * Also specify the minimum oom score and process state the application
     * should have, as seen by the cameraserver.
     *
     * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
     * the caller to specify the UID to use for permission/etc verification. This can only be
     * done by services trusted by the camera subsystem to act on behalf of applications and
     * to forward the real UID.</p>
     *
     * @param cameraId
     *             The unique identifier of the camera device to open
     * @param callback
     *             The callback which is invoked once the camera is opened
     * @param executor
     *             The executor which will be used when invoking the callback.
     * @param oomScoreOffset
     *             The minimum oom score that cameraservice must see for this client.
     * @param rotationOverride
     *             The type of rotation override (none, override_to_portrait, rotation_only)
     *             that should be followed for this camera id connection
     * @param sharedMode
     *             Parameter specifying if the camera should be opened in shared mode.
     *
     * @throws CameraAccessException if the camera is disabled by device policy,
     * has been disconnected, or is being used by a higher-priority camera API client in
     * non shared mode.
     *
     * @hide
     */
    public void openCameraImpl(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
            int oomScoreOffset, int rotationOverride)
            int oomScoreOffset, int rotationOverride, boolean sharedMode)
            throws CameraAccessException {

        if (cameraId == null) {
@@ -1407,24 +1505,7 @@ public final class CameraManager {
        }

        openCameraDeviceUserAsync(cameraId, callback, executor, oomScoreOffset,
                rotationOverride);
    }

    /**
     * Open a connection to a camera with the given ID, on behalf of another application.
     *
     * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
     * the caller to specify the UID to use for permission/etc verification. This can only be
     * done by services trusted by the camera subsystem to act on behalf of applications and
     * to forward the real UID.</p>
     *
     * @hide
     */
    public void openCameraImpl(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor)
            throws CameraAccessException {
        openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
                getRotationOverride(mContext));
                rotationOverride, sharedMode);
    }

    /**
@@ -2541,6 +2622,10 @@ public final class CameraManager {
                }
                @Override
                public void onCameraClosed(String id, int deviceId) {
                }
                @Override
                public void onCameraOpenedInSharedMode(String id, String clientPackageId,
                        int deviceId, boolean primaryClient) {
                }};

            String[] cameraIds;
@@ -3324,6 +3409,11 @@ public final class CameraManager {
            }
        }

        @Override
        public void onCameraOpenedInSharedMode(String cameraId, String clientPackageId,
                int deviceId, boolean primaryClient) {
        }

        @Override
        public void onCameraOpened(String cameraId, String clientPackageId, int deviceId) {
            synchronized (mLock) {
+32 −0
Original line number Diff line number Diff line
@@ -2120,6 +2120,38 @@ public abstract class CameraMetadata<TKey> {
     */
    public static final int AUTOMOTIVE_LOCATION_EXTRA_RIGHT = 10;

    //
    // Enumeration values for CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
    //

    /**
     * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final int SHARED_SESSION_COLOR_SPACE_UNSPECIFIED = -1;

    /**
     * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final int SHARED_SESSION_COLOR_SPACE_SRGB = 0;

    /**
     * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final int SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 = 7;

    /**
     * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
     * @hide
     */
    @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
    public static final int SHARED_SESSION_COLOR_SPACE_BT2020_HLG = 16;

    //
    // Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE
    //
Loading