diff --git a/api/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java b/api/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java index d6cc26414..2ec937507 100644 --- a/api/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/api/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java @@ -63,27 +63,6 @@ public class AndroidUtilities { }); } - /** Read a bitmap from the specified file, scaling if necessary - * Returns null if scaling failed after several tries */ - private static final int[] SAMPLE_SIZES = { 1, 2, 4, 6, 8, 10 }; - private static final int MAX_DIM = 1024; - public static Bitmap readScaledBitmap(String file) { - Bitmap bitmap = null; - int tries = 0; - BitmapFactory.Options opts = new BitmapFactory.Options(); - while((bitmap == null || (bitmap.getWidth() > MAX_DIM || bitmap.getHeight() > MAX_DIM)) && tries < SAMPLE_SIZES.length) { - opts.inSampleSize = SAMPLE_SIZES[tries]; - try { - bitmap = BitmapFactory.decodeFile(file, opts); - } catch (OutOfMemoryError e) { - log.error(e.getMessage(), e); - } - tries++; - } - - return bitmap; - } - /** * Start the given intent, handling security exceptions if they arise * @param request request code. if negative, no request. diff --git a/api/src/main/java/com/todoroo/astrid/data/RemoteModel.java b/api/src/main/java/com/todoroo/astrid/data/RemoteModel.java index 6b96bc806..57d7c91ba 100644 --- a/api/src/main/java/com/todoroo/astrid/data/RemoteModel.java +++ b/api/src/main/java/com/todoroo/astrid/data/RemoteModel.java @@ -6,24 +6,18 @@ package com.todoroo.astrid.data; import android.content.ContentValues; -import android.content.Context; -import android.graphics.Bitmap; import android.net.Uri; import android.text.TextUtils; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property.StringProperty; -import com.todoroo.andlib.utility.DateUtilities; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.tasks.files.FileHelper; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; /** * A model that is synchronized to a remote server and has a remote id @@ -91,35 +85,11 @@ abstract public class RemoteModel extends AbstractModel { public static class PictureHelper { - public static final String PICTURES_DIRECTORY = "pictures"; //$NON-NLS-1$ - - public static JSONObject savePictureJson(Context context, Bitmap bitmap) { + public static JSONObject savePictureJson(final Uri uri) { try { - String name = DateUtilities.now() + ".jpg"; - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", name); - jsonObject.put("type", "image/jpeg"); - - File dir = FileHelper.getExternalFilesDir(context, PICTURES_DIRECTORY); - if (dir != null) { - File file = new File(dir + File.separator + DateUtilities.now() + ".jpg"); - if (file.exists()) { - return null; - } - - try { - FileOutputStream fos = new FileOutputStream(file); - bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); - fos.flush(); - fos.close(); - jsonObject.put("path", file.getAbsolutePath()); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - return jsonObject; - } else { - return null; - } + return new JSONObject() {{ + put("uri", uri.toString()); + }}; } catch (JSONException e) { log.error(e.getMessage(), e); } @@ -131,10 +101,13 @@ abstract public class RemoteModel extends AbstractModel { if (value == null) { return null; } - if (value.contains("path")) { - JSONObject pictureJson = new JSONObject(value); - if (pictureJson.has("path")) { - String path = pictureJson.getString("path"); + if (value.contains("uri") || value.contains("path")) { + JSONObject json = new JSONObject(value); + if (json.has("uri")) { + return Uri.parse(json.getString("uri")); + } + if (json.has("path")) { + String path = json.getString("path"); return Uri.fromFile(new File(path)); } } diff --git a/api/src/main/java/org/tasks/files/FileHelper.java b/api/src/main/java/org/tasks/files/FileHelper.java index 97aaa19fa..9f3011e14 100644 --- a/api/src/main/java/org/tasks/files/FileHelper.java +++ b/api/src/main/java/org/tasks/files/FileHelper.java @@ -1,9 +1,17 @@ package org.tasks.files; +import android.app.Activity; import android.content.Context; +import android.database.Cursor; +import android.net.Uri; import android.os.Environment; +import android.provider.MediaStore; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; public class FileHelper { public static File getExternalFilesDir(Context context, String type) { @@ -14,4 +22,26 @@ public class FileHelper { return null; } + + public static String getPathFromUri(Activity activity, Uri uri) { + String[] projection = {MediaStore.Images.Media.DATA}; + Cursor cursor = activity.managedQuery(uri, projection, null, null, null); + + if (cursor != null) { + int column_index = cursor + .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } else { + return uri.getPath(); + } + } + + public static void copyFile(String from, String to) throws IOException { + FileChannel source = new FileInputStream(from).getChannel(); + FileChannel destination = new FileOutputStream(to).getChannel(); + destination.transferFrom(source, 0, source.size()); + destination.close(); + source.close(); + } } diff --git a/api/src/main/java/org/tasks/files/ImageHelper.java b/api/src/main/java/org/tasks/files/ImageHelper.java new file mode 100644 index 000000000..4e3446198 --- /dev/null +++ b/api/src/main/java/org/tasks/files/ImageHelper.java @@ -0,0 +1,42 @@ +package org.tasks.files; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +public class ImageHelper { + private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public static Bitmap sampleBitmap(String path, int reqWidth, int reqHeight) { + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(path, options); + } +} diff --git a/astrid/src/main/java/com/todoroo/astrid/actfm/ActFmCameraModule.java b/astrid/src/main/java/com/todoroo/astrid/actfm/ActFmCameraModule.java index 25cd03d29..905b60189 100644 --- a/astrid/src/main/java/com/todoroo/astrid/actfm/ActFmCameraModule.java +++ b/astrid/src/main/java/com/todoroo/astrid/actfm/ActFmCameraModule.java @@ -10,15 +10,12 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.Bitmap; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.support.v4.app.Fragment; import android.widget.ArrayAdapter; -import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import org.slf4j.Logger; @@ -29,6 +26,8 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import static org.tasks.files.FileHelper.getPathFromUri; + public class ActFmCameraModule { private static final Logger log = LoggerFactory.getLogger(ActFmCameraModule.class); @@ -103,57 +102,28 @@ public class ActFmCameraModule { } public interface CameraResultCallback { - public void handleCameraResult(Bitmap bitmap); - } - - private static Bitmap bitmapFromUri(Activity activity, Uri uri) { - String[] projection = { MediaStore.Images.Media.DATA }; - Cursor cursor = activity.managedQuery(uri, projection, null, null, null); - String path; - - if(cursor != null) { - int column_index = cursor - .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - cursor.moveToFirst(); - path = cursor.getString(column_index); - } else { - path = uri.getPath(); - } - - return AndroidUtilities.readScaledBitmap(path); + public void handleCameraResult(Uri uri); } public static boolean activityResult(Activity activity, int requestCode, int resultCode, Intent data, CameraResultCallback cameraResult) { if(requestCode == ActFmCameraModule.REQUEST_CODE_CAMERA && resultCode == Activity.RESULT_OK) { - Bitmap bitmap; - if (data == null) { // large from camera - if (lastTempFile != null) { - bitmap = bitmapFromUri(activity, Uri.fromFile(lastTempFile)); - lastTempFile.deleteOnExit(); - lastTempFile = null; - } - else { - bitmap = null; - } - } else { - bitmap = data.getParcelableExtra("data"); //$NON-NLS-1$ - } - if(bitmap != null) { + if (lastTempFile != null) { + Uri uri = Uri.fromFile(lastTempFile); + lastTempFile = null; activity.setResult(Activity.RESULT_OK); - cameraResult.handleCameraResult(bitmap); + cameraResult.handleCameraResult(uri); } return true; } else if(requestCode == ActFmCameraModule.REQUEST_CODE_PICTURE && resultCode == Activity.RESULT_OK) { Uri uri = data.getData(); - Bitmap bitmap = bitmapFromUri(activity, uri); - if(bitmap != null) { + String path = getPathFromUri(activity, uri); + if (new File(path).exists()) { activity.setResult(Activity.RESULT_OK); - cameraResult.handleCameraResult(bitmap); + cameraResult.handleCameraResult(uri); } return true; } return false; } - } diff --git a/astrid/src/main/java/com/todoroo/astrid/actfm/TagSettingsActivity.java b/astrid/src/main/java/com/todoroo/astrid/actfm/TagSettingsActivity.java index 5999484c9..feab4282c 100644 --- a/astrid/src/main/java/com/todoroo/astrid/actfm/TagSettingsActivity.java +++ b/astrid/src/main/java/com/todoroo/astrid/actfm/TagSettingsActivity.java @@ -8,7 +8,7 @@ package com.todoroo.astrid.actfm; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; +import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.text.TextUtils; @@ -229,7 +229,7 @@ public class TagSettingsActivity extends InjectingActionBarActivity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { CameraResultCallback callback = new CameraResultCallback() { @Override - public void handleCameraResult(Bitmap bitmap) { + public void handleCameraResult(Uri uri) { log.error("Not expecting this"); } }; diff --git a/astrid/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java b/astrid/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java index eb3560a34..1b8d9f103 100755 --- a/astrid/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java +++ b/astrid/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java @@ -12,7 +12,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.graphics.Bitmap; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -90,7 +90,6 @@ import org.tasks.notifications.NotificationManager; import org.tasks.preferences.ActivityPreferences; import java.io.File; -import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -99,6 +98,9 @@ import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; +import static org.tasks.files.FileHelper.copyFile; +import static org.tasks.files.FileHelper.getPathFromUri; + /** * This activity is responsible for creating new tasks and editing existing * ones. It saves a task when it is paused (screen rotated, back button pressed) @@ -882,18 +884,13 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { createNewFileAttachment(path, name, type); } - private void attachImage(Bitmap bitmap) { - + private void attachImage(String input) { AtomicReference nameRef = new AtomicReference<>(); - String path = FileUtilities.getNewImageAttachmentPath(preferences, getActivity(), nameRef); - try { - FileOutputStream fos = new FileOutputStream(path); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); - fos.flush(); - fos.close(); - - createNewFileAttachment(path, nameRef.get(), TaskAttachment.FILE_TYPE_IMAGE + "png"); + String path = FileUtilities.getNewImageAttachmentPath(preferences, getActivity(), nameRef); + copyFile(input, path); + String extension = path.substring(path.lastIndexOf('.') + 1); + createNewFileAttachment(path, nameRef.get(), TaskAttachment.FILE_TYPE_IMAGE + extension); } catch (Exception e) { log.error(e.getMessage(), e); Toast.makeText(getActivity(), R.string.file_err_copy, Toast.LENGTH_LONG).show(); @@ -997,8 +994,8 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { ActFmCameraModule.activityResult(getActivity(), requestCode, resultCode, data, new CameraResultCallback() { @Override - public void handleCameraResult(Bitmap bitmap) { - attachImage(bitmap); + public void handleCameraResult(Uri uri) { + attachImage(getPathFromUri(getActivity(), uri)); } }); diff --git a/astrid/src/main/java/com/todoroo/astrid/adapter/UpdateAdapter.java b/astrid/src/main/java/com/todoroo/astrid/adapter/UpdateAdapter.java index a6f9e6062..c3f32ba7a 100644 --- a/astrid/src/main/java/com/todoroo/astrid/adapter/UpdateAdapter.java +++ b/astrid/src/main/java/com/todoroo/astrid/adapter/UpdateAdapter.java @@ -32,6 +32,9 @@ import com.todoroo.astrid.data.UserActivity; import org.tasks.R; +import static org.tasks.files.FileHelper.getPathFromUri; +import static org.tasks.files.ImageHelper.sampleBitmap; + /** * Adapter for displaying a user's activity * @@ -177,7 +180,8 @@ public class UpdateAdapter extends CursorAdapter { final Fragment fragment) { if (updateBitmap != null) { //$NON-NLS-1$ commentPictureView.setVisibility(View.VISIBLE); - commentPictureView.setImageURI(updateBitmap); + String path = getPathFromUri(fragment.getActivity(), updateBitmap); + commentPictureView.setImageBitmap(sampleBitmap(path, commentPictureView.getLayoutParams().width, commentPictureView.getLayoutParams().height)); view.setOnClickListener(new OnClickListener() { @Override diff --git a/astrid/src/main/java/com/todoroo/astrid/notes/EditNoteActivity.java b/astrid/src/main/java/com/todoroo/astrid/notes/EditNoteActivity.java index a7df628d4..be1dd190b 100644 --- a/astrid/src/main/java/com/todoroo/astrid/notes/EditNoteActivity.java +++ b/astrid/src/main/java/com/todoroo/astrid/notes/EditNoteActivity.java @@ -8,7 +8,6 @@ package com.todoroo.astrid.notes; import android.app.Activity; import android.content.Intent; import android.database.sqlite.SQLiteException; -import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.support.v4.app.Fragment; @@ -64,6 +63,8 @@ import java.util.LinkedList; import java.util.List; import static org.tasks.date.DateTimeUtils.newDate; +import static org.tasks.files.FileHelper.getPathFromUri; +import static org.tasks.files.ImageHelper.sampleBitmap; public class EditNoteActivity extends LinearLayout implements TimerActionListener { @@ -80,7 +81,7 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene private View commentButton; private int commentItems = 10; private ImageButton pictureButton; - private Bitmap pendingCommentPicture = null; + private Uri pendingCommentPicture = null; private final Fragment fragment; private final AstridActivity activity; @@ -248,10 +249,10 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene } if (activity != null) { - Bitmap bitmap = activity.getIntent().getParcelableExtra(TaskEditFragment.TOKEN_PICTURE_IN_PROGRESS); - if (bitmap != null) { - pendingCommentPicture = bitmap; - pictureButton.setImageBitmap(pendingCommentPicture); + String uri = activity.getIntent().getStringExtra(TaskEditFragment.TOKEN_PICTURE_IN_PROGRESS); + if (uri != null) { + pendingCommentPicture = Uri.parse(uri); + setPictureButtonToPendingPicture(); } } } @@ -379,7 +380,7 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene userActivity.setTargetName(title); userActivity.setCreatedAt(DateUtilities.now()); if (usePicture && pendingCommentPicture != null) { - JSONObject pictureJson = RemoteModel.PictureHelper.savePictureJson(activity, pendingCommentPicture); + JSONObject pictureJson = RemoteModel.PictureHelper.savePictureJson(pendingCommentPicture); if (pictureJson != null) { userActivity.setPicture(pictureJson.toString()); } @@ -485,12 +486,12 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene respondToPicture = false; CameraResultCallback callback = new CameraResultCallback() { @Override - public void handleCameraResult(Bitmap bitmap) { + public void handleCameraResult(Uri uri) { if (activity != null) { - activity.getIntent().putExtra(TaskEditFragment.TOKEN_PICTURE_IN_PROGRESS, bitmap); + activity.getIntent().putExtra(TaskEditFragment.TOKEN_PICTURE_IN_PROGRESS, uri.toString()); } - pendingCommentPicture = bitmap; - pictureButton.setImageBitmap(pendingCommentPicture); + pendingCommentPicture = uri; + setPictureButtonToPendingPicture(); commentField.requestFocus(); } }; @@ -502,4 +503,8 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene } } + private void setPictureButtonToPendingPicture() { + String path = getPathFromUri(activity, pendingCommentPicture); + pictureButton.setImageBitmap(sampleBitmap(path, pictureButton.getWidth(), pictureButton.getHeight())); + } }