/* * Copyright (C) 2009 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 com.soundcloud.android.crop; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.media.ExifInterface; import android.net.Uri; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.support.annotation.Nullable; import android.text.TextUtils; import java.io.Closeable; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /* * Modified from original in AOSP. */ class CropUtil { private static final String SCHEME_FILE = "file"; private static final String SCHEME_CONTENT = "content"; public static void closeSilently(@Nullable Closeable c) { if (c == null) return; try { c.close(); } catch (Throwable t) { // Do nothing } } public static int getExifRotation(File imageFile) { if (imageFile == null) return 0; try { ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); // We only recognize a subset of orientation tag values switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) { case ExifInterface.ORIENTATION_ROTATE_90: return 90; case ExifInterface.ORIENTATION_ROTATE_180: return 180; case ExifInterface.ORIENTATION_ROTATE_270: return 270; default: return ExifInterface.ORIENTATION_UNDEFINED; } } catch (IOException e) { Log.e("Error getting Exif data", e); return 0; } } public static boolean copyExifRotation(File sourceFile, File destFile) { if (sourceFile == null || destFile == null) return false; try { ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath()); ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath()); exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION)); exifDest.saveAttributes(); return true; } catch (IOException e) { Log.e("Error copying Exif data", e); return false; } } @Nullable public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) { if (uri == null) return null; if (SCHEME_FILE.equals(uri.getScheme())) { return new File(uri.getPath()); } else if (SCHEME_CONTENT.equals(uri.getScheme())) { final String[] filePathColumn = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME }; Cursor cursor = null; try { cursor = resolver.query(uri, filePathColumn, null, null, null); if (cursor != null && cursor.moveToFirst()) { final int columnIndex = (uri.toString().startsWith("content://com.google.android.gallery3d")) ? cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) : cursor.getColumnIndex(MediaStore.MediaColumns.DATA); // Picasa images on API 13+ if (columnIndex != -1) { String filePath = cursor.getString(columnIndex); if (!TextUtils.isEmpty(filePath)) { return new File(filePath); } } } } catch (IllegalArgumentException e) { // Google Drive images return getFromMediaUriPfd(context, resolver, uri); } catch (SecurityException ignored) { // Nothing we can do } finally { if (cursor != null) cursor.close(); } } return null; } private static String getTempFilename(Context context) throws IOException { File outputDir = context.getCacheDir(); File outputFile = File.createTempFile("image", "tmp", outputDir); return outputFile.getAbsolutePath(); } @Nullable private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) { if (uri == null) return null; FileInputStream input = null; FileOutputStream output = null; try { ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); FileDescriptor fd = pfd.getFileDescriptor(); input = new FileInputStream(fd); String tempFilename = getTempFilename(context); output = new FileOutputStream(tempFilename); int read; byte[] bytes = new byte[4096]; while ((read = input.read(bytes)) != -1) { output.write(bytes, 0, read); } return new File(tempFilename); } catch (IOException ignored) { // Nothing we can do } finally { closeSilently(input); closeSilently(output); } return null; } public static void startBackgroundJob(MonitoredActivity activity, String title, String message, Runnable job, Handler handler) { // Make the progress dialog uncancelable, so that we can guarantee // the thread will be done before the activity getting destroyed ProgressDialog dialog = ProgressDialog.show( activity, title, message, true, false); new Thread(new BackgroundJob(activity, job, dialog, handler)).start(); } private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable { private final MonitoredActivity activity; private final ProgressDialog dialog; private final Runnable job; private final Handler handler; private final Runnable cleanupRunner = new Runnable() { public void run() { activity.removeLifeCycleListener(BackgroundJob.this); if (dialog.getWindow() != null) dialog.dismiss(); } }; public BackgroundJob(MonitoredActivity activity, Runnable job, ProgressDialog dialog, Handler handler) { this.activity = activity; this.dialog = dialog; this.job = job; this.activity.addLifeCycleListener(this); this.handler = handler; } public void run() { try { job.run(); } finally { handler.post(cleanupRunner); } } @Override public void onActivityDestroyed(MonitoredActivity activity) { // We get here only when the onDestroyed being called before // the cleanupRunner. So, run it now and remove it from the queue cleanupRunner.run(); handler.removeCallbacks(cleanupRunner); } @Override public void onActivityStopped(MonitoredActivity activity) { dialog.hide(); } @Override public void onActivityStarted(MonitoredActivity activity) { dialog.show(); } } }