package com.yalantis.ucrop.task; import android.Manifest.permission; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.ParcelFileDescriptor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import android.text.TextUtils; import android.util.Log; import com.yalantis.ucrop.callback.BitmapLoadCallback; import com.yalantis.ucrop.model.ExifInfo; import com.yalantis.ucrop.util.BitmapLoadUtils; import com.yalantis.ucrop.util.FileUtils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; /** * Creates and returns a Bitmap for a given Uri(String url). * inSampleSize is calculated based on requiredWidth property. However can be adjusted if OOM occurs. * If any EXIF config is found - bitmap is transformed properly. */ public class BitmapLoadTask extends AsyncTask { private static final String TAG = "BitmapWorkerTask"; private final Context mContext; private Uri mInputUri; private Uri mOutputUri; private final int mRequiredWidth; private final int mRequiredHeight; private final BitmapLoadCallback mBitmapLoadCallback; public static class BitmapWorkerResult { Bitmap mBitmapResult; ExifInfo mExifInfo; Exception mBitmapWorkerException; public BitmapWorkerResult(@NonNull Bitmap bitmapResult, @NonNull ExifInfo exifInfo) { mBitmapResult = bitmapResult; mExifInfo = exifInfo; } public BitmapWorkerResult(@NonNull Exception bitmapWorkerException) { mBitmapWorkerException = bitmapWorkerException; } } public BitmapLoadTask(@NonNull Context context, @NonNull Uri inputUri, @Nullable Uri outputUri, int requiredWidth, int requiredHeight, BitmapLoadCallback loadCallback) { mContext = context; mInputUri = inputUri; mOutputUri = outputUri; mRequiredWidth = requiredWidth; mRequiredHeight = requiredHeight; mBitmapLoadCallback = loadCallback; } @Override @NonNull protected BitmapWorkerResult doInBackground(Void... params) { if (mInputUri == null) { return new BitmapWorkerResult(new NullPointerException("Input Uri cannot be null")); } try { processInputUri(); } catch (NullPointerException | IOException e) { return new BitmapWorkerResult(e); } final ParcelFileDescriptor parcelFileDescriptor; try { parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(mInputUri, "r"); } catch (FileNotFoundException e) { return new BitmapWorkerResult(e); } final FileDescriptor fileDescriptor; if (parcelFileDescriptor != null) { fileDescriptor = parcelFileDescriptor.getFileDescriptor(); } else { return new BitmapWorkerResult(new NullPointerException("ParcelFileDescriptor was null for given Uri: [" + mInputUri + "]")); } final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); if (options.outWidth == -1 || options.outHeight == -1) { return new BitmapWorkerResult(new IllegalArgumentException("Bounds for bitmap could not be retrieved from the Uri: [" + mInputUri + "]")); } options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight); options.inJustDecodeBounds = false; Bitmap decodeSampledBitmap = null; boolean decodeAttemptSuccess = false; while (!decodeAttemptSuccess) { try { decodeSampledBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); decodeAttemptSuccess = true; } catch (OutOfMemoryError error) { Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error); options.inSampleSize *= 2; } } if (decodeSampledBitmap == null) { return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]")); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { BitmapLoadUtils.close(parcelFileDescriptor); } int exifOrientation = BitmapLoadUtils.getExifOrientation(mContext, mInputUri); int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation); int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation); ExifInfo exifInfo = new ExifInfo(exifOrientation, exifDegrees, exifTranslation); Matrix matrix = new Matrix(); if (exifDegrees != 0) { matrix.preRotate(exifDegrees); } if (exifTranslation != 1) { matrix.postScale(exifTranslation, 1); } if (!matrix.isIdentity()) { return new BitmapWorkerResult(BitmapLoadUtils.transformBitmap(decodeSampledBitmap, matrix), exifInfo); } return new BitmapWorkerResult(decodeSampledBitmap, exifInfo); } private void processInputUri() throws NullPointerException, IOException { String inputUriScheme = mInputUri.toString(); Log.d(TAG, "Uri scheme: " + inputUriScheme); if (inputUriScheme.startsWith("http") || inputUriScheme.startsWith("https")) { try { downloadFile(mInputUri, mOutputUri); } catch (NullPointerException | IOException e) { Log.e(TAG, "Downloading failed", e); throw e; } } else { String path = getFilePath(); if (!TextUtils.isEmpty(path) && new File(path).exists()) { mInputUri = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? mInputUri : Uri.fromFile(new File(path)); } else { try { copyFile(mInputUri, mOutputUri); } catch (NullPointerException | IOException e) { Log.e(TAG, "Copying failed", e); throw e; } } } } private String getFilePath() { if (ContextCompat.checkSelfPermission(mContext, permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return FileUtils.getPath(mContext, mInputUri); } else { return null; } } private void copyFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException { Log.d(TAG, "copyFile"); if (outputUri == null) { throw new NullPointerException("Output Uri is null - cannot copy image"); } InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = mContext.getContentResolver().openInputStream(inputUri); outputStream = new FileOutputStream(new File(outputUri.getPath())); if (inputStream == null) { throw new NullPointerException("InputStream for given input Uri is null"); } byte buffer[] = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } } finally { BitmapLoadUtils.close(outputStream); BitmapLoadUtils.close(inputStream); // swap uris, because input image was copied to the output destination // (cropped image will override it later) mInputUri = mOutputUri; } } private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException { Log.d(TAG, "downloadFile"); if (outputUri == null) { throw new NullPointerException("Output Uri is null - cannot download image"); } try { URL u = new URL(inputUri.toString()); byte[] buffer = new byte[1024]; int read; BufferedInputStream bin; bin = new BufferedInputStream(u.openStream()); OutputStream outputStream = mContext.getContentResolver().openOutputStream(outputUri); BufferedOutputStream bout = new BufferedOutputStream( outputStream); while ((read = bin.read(buffer)) > -1) { bout.write(buffer, 0, read); } bout.flush(); bout.close(); bin.close(); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } finally { } mInputUri = mOutputUri; } // private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException { // Log.d(TAG, "downloadFile"); // // if (outputUri == null) { // throw new NullPointerException("Output Uri is null - cannot download image"); // } // // OkHttpClient client = new OkHttpClient(); // // BufferedSource source = null; // Sink sink = null; // Response response = null; // try { // Request request = new Request.Builder() // .url(inputUri.toString()) // .build(); // response = client.newCall(request).execute(); // source = response.body().source(); // // OutputStream outputStream = mContext.getContentResolver().openOutputStream(outputUri); // if (outputStream != null) { // sink = Okio.sink(outputStream); // source.readAll(sink); // } else { // throw new NullPointerException("OutputStream for given output Uri is null"); // } // } finally { // BitmapLoadUtils.close(source); // BitmapLoadUtils.close(sink); // if (response != null) { // BitmapLoadUtils.close(response.body()); // } // client.dispatcher().cancelAll(); // // // swap uris, because input image was downloaded to the output destination // // (cropped image will override it later) // mInputUri = mOutputUri; // } // } @Override protected void onPostExecute(@NonNull BitmapWorkerResult result) { if (result.mBitmapWorkerException == null) { mBitmapLoadCallback.onBitmapLoaded(result.mBitmapResult, result.mExifInfo, mInputUri, (mOutputUri == null) ? null : mOutputUri); } else { mBitmapLoadCallback.onFailure(result.mBitmapWorkerException); } } }