<commit_before>/*
 * Copyright (C) 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 android.content.ContentValues;
import android.graphics.ImageFormat;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageUtil.CodecFailedException;
import androidx.camera.core.impl.utils.Exif;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;

final class ImageSaver implements Runnable {
    private static final String TAG = "ImageSaver";

    private static final String TEMP_FILE_PREFIX = "CameraX";
    private static final String TEMP_FILE_SUFFIX = ".tmp";
    private static final int COPY_BUFFER_SIZE = 1024;
    private static final int PENDING = 1;
    private static final int NOT_PENDING = 0;

    // The image that was captured
    private final ImageProxy mImage;
    // The orientation of the image
    private final int mOrientation;
    // The target location to save the image to.
    @NonNull
    private final ImageCapture.OutputFileOptions mOutputFileOptions;
    // The executor to call back on
    private final Executor mExecutor;
    // The callback to call on completion
    final OnImageSavedCallback mCallback;

    ImageSaver(
            ImageProxy image,
            @NonNull ImageCapture.OutputFileOptions outputFileOptions,
            int orientation,
            Executor executor,
            OnImageSavedCallback callback) {
        mImage = image;
        mOutputFileOptions = outputFileOptions;
        mOrientation = orientation;
        mCallback = callback;
        mExecutor = executor;
    }

    @Override
    public void run() {
        // Finally, we save the file to disk
        SaveError saveError = null;
        String errorMessage = null;
        Exception exception = null;

        File file;
        Uri outputUri = null;
        try {
            // Create a temp file if the save location is not a file. This is necessary because
            // ExifInterface only supports File.
            file = isSaveToFile() ? mOutputFileOptions.getFile() :
                    File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
        } catch (IOException e) {
            postError(SaveError.FILE_IO_FAILED, "Failed to create temp file", e);
            return;
        }

        try (ImageProxy imageToClose = mImage;
             FileOutputStream output = new FileOutputStream(file)) {
            byte[] bytes = ImageUtil.imageToJpegByteArray(mImage);
            output.write(bytes);

            Exif exif = Exif.createFromFile(file);
            exif.attachTimestamp();

            // Use exif for orientation (contains rotation only) from the original image if JPEG,
            // because imageToJpegByteArray removes EXIF in certain conditions. See b/124280392
            if (mImage.getFormat() == ImageFormat.JPEG) {
                ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
                // Rewind to make sure it is at the beginning of the buffer
                buffer.rewind();

                byte[] data = new byte[buffer.capacity()];
                buffer.get(data);
                InputStream inputStream = new ByteArrayInputStream(data);
                Exif originalExif = Exif.createFromInputStream(inputStream);

                exif.setOrientation(originalExif.getOrientation());
            } else {
                exif.rotate(mOrientation);
            }

            ImageCapture.Metadata metadata = mOutputFileOptions.getMetadata();
            if (metadata.isReversedHorizontal()) {
                exif.flipHorizontally();
            }
            if (metadata.isReversedVertical()) {
                exif.flipVertically();
            }
            if (metadata.getLocation() != null) {
                exif.attachLocation(mOutputFileOptions.getMetadata().getLocation());
            }

            exif.save();

            if (isSaveToMediaStore()) {
                outputUri = mOutputFileOptions.getContentResolver().insert(
                        mOutputFileOptions.getSaveCollection(),
                        mOutputFileOptions.getContentValues());
                setUriPending(outputUri, PENDING);
                if (!copyTempFileToUri(file, outputUri)) {
                    saveError = SaveError.FILE_IO_FAILED;
                    errorMessage = "Failed to save to Uri";
                }
                setUriPending(outputUri, NOT_PENDING);
            } else if (isSaveToOutputStream()) {
                copyTempFileToOutputStream(file, mOutputFileOptions.getOutputStream());
            }

            // Write temp file to Uri
        } catch (IOException e) {
            saveError = SaveError.FILE_IO_FAILED;
            errorMessage = "Failed to write or close the file";
            exception = e;
            if (outputUri != null) {
                // If error saving to URI, delete the URI row created.
                mOutputFileOptions.getContentResolver().delete(outputUri, null, null);
            }
        } catch (CodecFailedException e) {
            switch (e.getFailureType()) {
                case ENCODE_FAILED:
                    saveError = SaveError.ENCODE_FAILED;
                    errorMessage = "Failed to encode mImage";
                    break;
                case DECODE_FAILED:
                    saveError = SaveError.CROP_FAILED;<commit_msg>Log exceptions in ImageSaver for debugging<commit_after>/*
 * Copyright (C) 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 android.content.ContentValues;
import android.graphics.ImageFormat;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageUtil.CodecFailedException;
import androidx.camera.core.impl.utils.Exif;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;

final class ImageSaver implements Runnable {
    private static final String TAG = "ImageSaver";

    private static final String TEMP_FILE_PREFIX = "CameraX";
    private static final String TEMP_FILE_SUFFIX = ".tmp";
    private static final int COPY_BUFFER_SIZE = 1024;
    private static final int PENDING = 1;
    private static final int NOT_PENDING = 0;

    // The image that was captured
    private final ImageProxy mImage;
    // The orientation of the image
    private final int mOrientation;
    // The target location to save the image to.
    @NonNull
    private final ImageCapture.OutputFileOptions mOutputFileOptions;
    // The executor to call back on
    private final Executor mExecutor;
    // The callback to call on completion
    final OnImageSavedCallback mCallback;

    ImageSaver(
            ImageProxy image,
            @NonNull ImageCapture.OutputFileOptions outputFileOptions,
            int orientation,
            Executor executor,
            OnImageSavedCallback callback) {
        mImage = image;
        mOutputFileOptions = outputFileOptions;
        mOrientation = orientation;
        mCallback = callback;
        mExecutor = executor;
    }

    @Override
    public void run() {
        // Finally, we save the file to disk
        SaveError saveError = null;
        String errorMessage = null;
        Exception exception = null;

        File file;
        Uri outputUri = null;
        try {
            // Create a temp file if the save location is not a file. This is necessary because
            // ExifInterface only supports File.
            file = isSaveToFile() ? mOutputFileOptions.getFile() :
                    File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
        } catch (IOException e) {
            Log.e(TAG, "Failed to create temp file", e);
            postError(SaveError.FILE_IO_FAILED, "Failed to create temp file", e);
            return;
        }

        try (ImageProxy imageToClose = mImage;
             FileOutputStream output = new FileOutputStream(file)) {
            byte[] bytes = ImageUtil.imageToJpegByteArray(mImage);
            output.write(bytes);

            Exif exif = Exif.createFromFile(file);
            exif.attachTimestamp();

            // Use exif for orientation (contains rotation only) from the original image if JPEG,
            // because imageToJpegByteArray removes EXIF in certain conditions. See b/124280392
            if (mImage.getFormat() == ImageFormat.JPEG) {
                ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
                // Rewind to make sure it is at the beginning of the buffer
                buffer.rewind();

                byte[] data = new byte[buffer.capacity()];
                buffer.get(data);
                InputStream inputStream = new ByteArrayInputStream(data);
                Exif originalExif = Exif.createFromInputStream(inputStream);

                exif.setOrientation(originalExif.getOrientation());
            } else {
                exif.rotate(mOrientation);
            }

            ImageCapture.Metadata metadata = mOutputFileOptions.getMetadata();
            if (metadata.isReversedHorizontal()) {
                exif.flipHorizontally();
            }
            if (metadata.isReversedVertical()) {
                exif.flipVertically();
            }
            if (metadata.getLocation() != null) {
                exif.attachLocation(mOutputFileOptions.getMetadata().getLocation());
            }

            exif.save();

            if (isSaveToMediaStore()) {
                outputUri = mOutputFileOptions.getContentResolver().insert(
                        mOutputFileOptions.getSaveCollection(),
                        mOutputFileOptions.getContentValues());
                setUriPending(outputUri, PENDING);
                if (!copyTempFileToUri(file, outputUri)) {
                    saveError = SaveError.FILE_IO_FAILED;
                    errorMessage = "Failed to save to Uri";
                }
                setUriPending(outputUri, NOT_PENDING);
            } else if (isSaveToOutputStream()) {
                copyTempFileToOutputStream(file, mOutputFileOptions.getOutputStream());
            }

            // Write temp file to Uri
        } catch (IOException e) {
            Log.e(TAG, "Failed to save image", e);
            saveError = SaveError.FILE_IO_FAILED;
            errorMessage = "Failed to write or close the file";
            exception = e;
            if (outputUri != null) {
                // If error saving to URI, delete the URI row created.
                mOutputFileOptions.getContentResolver().delete(outputUri, null, null);
            }
        } catch (CodecFailedException e) {
            Log.e(TAG, "Failed to save image", e);
            switch (e.getFailureType()) {
                case ENCODE_FAILED:
                    saveError = SaveError.ENCODE_FAILED;
                    errorMessage = "Failed to encode mImage";
                    break;
                case DECODE_FAILED:
                    saveError = SaveError.CROP_FAILED;
{'fraction_non_alphanumeric': 0.07767372314472339, 'fraction_numerical': 0.0037774455024789486, 'mean_word_length': 2.1977856064418724, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7590826', 'n_tokens_mistral': 3246, 'n_tokens_neox': 3123, 'n_words': 959}