From f51d59a0f6b79dfb610ab3765ed349d05503cc9c Mon Sep 17 00:00:00 2001 From: Andrew Stephan Date: Tue, 1 Apr 2014 18:59:51 -0400 Subject: [PATCH] Moved resizing images to a background thread and showed a progress dialog until they are done --- .../src/MultiImageChooserActivity.java | 468 ++++++++++++------ .../synconset/ImagePicker/ImagePicker.java | 125 +---- 2 files changed, 321 insertions(+), 272 deletions(-) diff --git a/src/android/Library/src/MultiImageChooserActivity.java b/src/android/Library/src/MultiImageChooserActivity.java index 4b5e3cd..15b3feb 100644 --- a/src/android/Library/src/MultiImageChooserActivity.java +++ b/src/android/Library/src/MultiImageChooserActivity.java @@ -30,8 +30,14 @@ package com.synconset; +import java.net.URI; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import com.synconset.FakeR; @@ -39,6 +45,7 @@ import android.app.Activity; import android.app.ActionBar; import android.app.AlertDialog; import android.app.LoaderManager; +import android.app.ProgressDialog; import android.content.Context; import android.content.CursorLoader; import android.content.DialogInterface; @@ -48,6 +55,9 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; +import android.graphics.Matrix; +import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; @@ -77,6 +87,9 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi public static final int NOLIMIT = -1; public static final String MAX_IMAGES_KEY = "MAX_IMAGES"; + public static final String WIDTH_KEY = "WIDTH"; + public static final String HEIGHT_KEY = "HEIGHT"; + public static final String QUALITY_KEY = "QUALITY"; private ImageAdapter ia; @@ -93,6 +106,10 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi private int maxImages; private int maxImageCount; + + private int desiredWidth; + private int desiredHeight; + private int quality; private GridView gridView; @@ -102,6 +119,8 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi private boolean shouldRequestThumb = true; private FakeR fakeR; + + private ProgressDialog progress; @Override public void onCreate(Bundle savedInstanceState) { @@ -111,6 +130,9 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi fileNames.clear(); maxImages = getIntent().getIntExtra(MAX_IMAGES_KEY, NOLIMIT); + desiredWidth = getIntent().getIntExtra(WIDTH_KEY, 0); + desiredHeight = getIntent().getIntExtra(HEIGHT_KEY, 0); + quality = getIntent().getIntExtra(QUALITY_KEY, 0); maxImageCount = maxImages; colWidth = getIntent().getIntExtra(COL_WIDTH_KEY, DEFAULT_COLUMN_WIDTH); @@ -120,19 +142,15 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi colWidth = width / 4; - // int bgColor = getIntent().getIntExtra("BG_COLOR", Color.BLACK); gridView = (GridView) findViewById(fakeR.getId("id", "gridview")); - //gridView.setColumnWidth(colWidth); gridView.setOnItemClickListener(this); gridView.setOnScrollListener(new OnScrollListener() { - private int lastFirstItem = 0; private long timestamp = System.currentTimeMillis(); @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE) { - // Log.d(TAG, "IDLE - Reload!"); shouldRequestThumb = true; ia.notifyDataSetChanged(); } @@ -145,18 +163,13 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi double speed = 1 / dt * 1000; lastFirstItem = firstVisibleItem; timestamp = System.currentTimeMillis(); - // Log.d(TAG, "Speed: " + speed + " elements/second"); - // Limitarlo si vamos a más de una página por segundo... + // Limit if we go faster than a page a second shouldRequestThumb = speed < visibleItemCount; } } }); selectedColor = 0xff32b2e1; - // selectedColor = Color.RED; - - // gridView.setBackgroundColor(bgColor); - // gridView.setBackgroundResource(R.drawable.grid_background); ia = new ImageAdapter(this); gridView.setAdapter(ia); @@ -166,6 +179,149 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi getLoaderManager().initLoader(CURSORLOADER_REAL, null, this); setupHeader(); updateAcceptButton(); + progress = new ProgressDialog(this); + progress.setTitle("Processing Images"); + progress.setMessage("This may take a few moments"); + } + + @Override + public void onItemClick(AdapterView arg0, View view, int position, long id) { + String name = getImageName(position); + + if (name == null) { + return; + } + boolean isChecked = !isChecked(position); + if (maxImages == 0 && isChecked) { + isChecked = false; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Maximum " + maxImageCount + " Photos"); + builder.setMessage("You can only select " + maxImageCount + " photos at a time."); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + if (isChecked) { + if (fileNames.add(name)) { + if (maxImageCount == 1) { + this.selectClicked(null); + } else { + maxImages--; + ImageView imageView = (ImageView)view; + if (android.os.Build.VERSION.SDK_INT>=16) { + imageView.setImageAlpha(128); + } else { + imageView.setAlpha(128); + } + view.setBackgroundColor(selectedColor); + } + } + } else { + if (fileNames.remove(name)) { + // Solo incrementa los slots libres si hemos + // "liberado" uno... + maxImages++; + ImageView imageView = (ImageView)view; + if (android.os.Build.VERSION.SDK_INT>=16) { + imageView.setImageAlpha(255); + } else { + imageView.setAlpha(255); + } + view.setBackgroundColor(Color.TRANSPARENT); + } + } + + checkStatus.put(position, isChecked); + updateAcceptButton(); + } + + @Override + public Loader onCreateLoader(int cursorID, Bundle arg1) { + CursorLoader cl = null; + + ArrayList img = new ArrayList(); + switch (cursorID) { + + case CURSORLOADER_THUMBS: + img.add(MediaStore.Images.Media._ID); + break; + case CURSORLOADER_REAL: + img.add(MediaStore.Images.Thumbnails.DATA); + break; + default: + break; + } + + cl = new CursorLoader(MultiImageChooserActivity.this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + img.toArray(new String[img.size()]), null, null, "DATE_MODIFIED DESC"); + return cl; + } + + @Override + public void onLoadFinished(Loader loader, Cursor cursor) { + if (cursor == null) { + // NULL cursor. This usually means there's no image database yet.... + return; + } + + switch (loader.getId()) { + case CURSORLOADER_THUMBS: + imagecursor = cursor; + image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID); + ia.notifyDataSetChanged(); + break; + case CURSORLOADER_REAL: + actualimagecursor = cursor; + actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + break; + default: + break; + } + } + + @Override + public void onLoaderReset(Loader loader) { + if (loader.getId() == CURSORLOADER_THUMBS) { + imagecursor = null; + } else if (loader.getId() == CURSORLOADER_REAL) { + actualimagecursor = null; + } + } + + + public void cancelClicked(View ignored) { + setResult(RESULT_CANCELED); + finish(); + } + + public void selectClicked(View ignored) { + ((TextView) getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done_textview"))).setEnabled(false); + getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done")).setEnabled(false); + progress.show(); + Intent data = new Intent(); + if (fileNames.isEmpty()) { + this.setResult(RESULT_CANCELED); + progress.dismiss(); + finish(); + } else { + new ResizeImagesTask().execute(fileNames); + } + } + + + /********************* + * Helper Methods + ********************/ + private void updateAcceptButton() { + ((TextView) getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done_textview"))) + .setEnabled(fileNames.size() != 0); + getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done")).setEnabled(fileNames.size() != 0); + } private void setupHeader() { @@ -212,8 +368,29 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } + + private String getImageName(int position) { + actualimagecursor.moveToPosition(position); + String name = null; + + try { + name = actualimagecursor.getString(actual_image_column_index); + } catch (Exception e) { + return null; + } + return name; + } + + public boolean isChecked(int position) { + boolean ret = checkStatus.get(position); + return ret; + } + - public class SquareImageView extends ImageView { + /********************* + * Nested Classes + ********************/ + private class SquareImageView extends ImageView { public SquareImageView(Context context) { super(context); } @@ -223,8 +400,9 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi super.onMeasure(widthMeasureSpec, widthMeasureSpec); } } - - public class ImageAdapter extends BaseAdapter { + + + private class ImageAdapter extends BaseAdapter { private final Bitmap mPlaceHolderBitmap; public ImageAdapter(Context c) { @@ -297,166 +475,128 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi return imageView; } } - - private String getImageName(int position) { - actualimagecursor.moveToPosition(position); - String name = null; - - try { - name = actualimagecursor.getString(actual_image_column_index); - } catch (Exception e) { - return null; - } - return name; - } - - private void setChecked(int position, boolean b) { - checkStatus.put(position, b); - } - - public boolean isChecked(int position) { - boolean ret = checkStatus.get(position); - return ret; - } - - public void cancelClicked(View ignored) { - setResult(RESULT_CANCELED); - finish(); - } - - public void selectClicked(View ignored) { - Intent data = new Intent(); - if (fileNames.isEmpty()) { - this.setResult(RESULT_CANCELED); - } else { + + + private class ResizeImagesTask extends AsyncTask, Void, ArrayList> { + @Override + protected ArrayList doInBackground(Set... fileSets) { + Set fileNames = fileSets[0]; ArrayList al = new ArrayList(); - al.addAll(fileNames); - Bundle res = new Bundle(); - res.putStringArrayList("MULTIPLEFILENAMES", al); - if (imagecursor != null) { - res.putInt("TOTALFILES", imagecursor.getCount()); - } - - data.putExtras(res); - this.setResult(RESULT_OK, data); - } - // Log.d(TAG, "Returning " + fileNames.size() + " items"); - finish(); - } - - @Override - public void onItemClick(AdapterView arg0, View view, int position, long id) { - String name = getImageName(position); - - if (name == null) { - return; - } - boolean isChecked = !isChecked(position); - if (maxImages == 0 && isChecked) { - isChecked = false; - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Maximum " + maxImageCount + " Photos"); - builder.setMessage("You can only select " + maxImageCount + " photos at a time."); - builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - AlertDialog alert = builder.create(); - alert.show(); - } - - if (isChecked) { - if (fileNames.add(name)) { - if (maxImageCount == 1) { - this.selectClicked(null); - } else { - maxImages--; - ImageView imageView = (ImageView)view; - if (android.os.Build.VERSION.SDK_INT>=16) { - imageView.setImageAlpha(128); - } else { - imageView.setAlpha(128); + try { + Iterator i = fileNames.iterator(); + while(i.hasNext()) { + File file = new File(i.next()); + Bitmap bmp = this.getBitmap(file); + int width = bmp.getWidth(); + int height = bmp.getHeight(); + float widthScale = 1.0f; + float heightScale = 1.0f; + float scale = 1.0f; + if (desiredWidth > 0 || desiredHeight > 0) { + if (desiredHeight == 0 && desiredWidth < width) { + scale = (float)desiredWidth/width; + } else if (desiredWidth == 0 && desiredHeight < height) { + scale = (float)desiredHeight/height; + } else { + if (desiredWidth > 0 && desiredWidth < width) { + widthScale = (float)desiredWidth/width; + } + if (desiredHeight > 0 && desiredHeight < height) { + heightScale = (float)desiredHeight/height; + } + if (widthScale < heightScale) { + scale = widthScale; + } else { + scale = heightScale; + } + } } - view.setBackgroundColor(selectedColor); + if (scale < 1) { + bmp = this.getResizedBitmap(bmp, scale); + } + file = this.storeImage(bmp, file.getName()); + al.add(Uri.fromFile(file).toString()); + } + return al; + } catch(IOException e) { + try { + for (int i = 0; i < al.size(); i++) { + URI uri = new URI(al.get(i)); + File file = new File(uri); + file.delete(); + } + } catch(Exception exception) { + // the finally does what we want to do + } finally { + return new ArrayList(); } } - } else { - if (fileNames.remove(name)) { - // Solo incrementa los slots libres si hemos - // "liberado" uno... - maxImages++; - ImageView imageView = (ImageView)view; - if (android.os.Build.VERSION.SDK_INT>=16) { - imageView.setImageAlpha(255); - } else { - imageView.setAlpha(255); + } + + @Override + protected void onPostExecute(ArrayList al) { + Intent data = new Intent(); + + if (al.size() > 0) { + Bundle res = new Bundle(); + res.putStringArrayList("MULTIPLEFILENAMES", al); + if (imagecursor != null) { + res.putInt("TOTALFILES", imagecursor.getCount()); } - view.setBackgroundColor(Color.TRANSPARENT); + + data.putExtras(res); + setResult(RESULT_OK, data); + } else { + setResult(RESULT_CANCELED, data); } + progress.dismiss(); + finish(); } - - setChecked(position, isChecked); - updateAcceptButton(); - } - - private void updateAcceptButton() { - ((TextView) getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done_textview"))) - .setEnabled(fileNames.size() != 0); - getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done")).setEnabled(fileNames.size() != 0); - - } - - @Override - public Loader onCreateLoader(int cursorID, Bundle arg1) { - CursorLoader cl = null; - - ArrayList img = new ArrayList(); - switch (cursorID) { - - case CURSORLOADER_THUMBS: - img.add(MediaStore.Images.Media._ID); - break; - case CURSORLOADER_REAL: - img.add(MediaStore.Images.Thumbnails.DATA); - break; - default: - break; + + /* + * The following functions are originally from + * https://github.com/raananw/PhoneGap-Image-Resizer + * + * They have been modified by Andrew Stephan for Sync OnSet + * + * The software is open source, MIT Licensed. + * Copyright (C) 2012, webXells GmbH All Rights Reserved. + */ + private File storeImage(Bitmap bmp, String fileName) throws IOException { + int index = fileName.lastIndexOf('.'); + String name = fileName.substring(0, index); + String ext = fileName.substring(index); + File file = File.createTempFile(name, ext); + OutputStream outStream = new FileOutputStream(file); + if (ext.compareToIgnoreCase(".png") == 0) { + bmp.compress(Bitmap.CompressFormat.PNG, quality, outStream); + } else { + bmp.compress(Bitmap.CompressFormat.JPEG, quality, outStream); + } + outStream.flush(); + outStream.close(); + return file; } - - cl = new CursorLoader(MultiImageChooserActivity.this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - img.toArray(new String[img.size()]), null, null, "DATE_MODIFIED DESC"); - return cl; - } - - @Override - public void onLoadFinished(Loader loader, Cursor cursor) { - if (cursor == null) { - // NULL cursor. This usually means there's no image database yet.... - return; + + private Bitmap getResizedBitmap(Bitmap bm, float factor) { + int width = bm.getWidth(); + int height = bm.getHeight(); + // create a matrix for the manipulation + Matrix matrix = new Matrix(); + // resize the bit map + matrix.postScale(factor, factor); + // recreate the new Bitmap + Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false); + return resizedBitmap; } - - switch (loader.getId()) { - case CURSORLOADER_THUMBS: - imagecursor = cursor; - image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID); - ia.notifyDataSetChanged(); - break; - case CURSORLOADER_REAL: - actualimagecursor = cursor; - actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - break; - default: - break; - } - } - - @Override - public void onLoaderReset(Loader loader) { - if (loader.getId() == CURSORLOADER_THUMBS) { - imagecursor = null; - } else if (loader.getId() == CURSORLOADER_REAL) { - actualimagecursor = null; + + private Bitmap getBitmap(File file) throws IOException { + Bitmap bmp; + bmp = BitmapFactory.decodeFile(file.getAbsolutePath()); + if (bmp == null) { + throw new IOException("The image file could not be opened."); + } + return bmp; } } } diff --git a/src/android/com/synconset/ImagePicker/ImagePicker.java b/src/android/com/synconset/ImagePicker/ImagePicker.java index f267920..4a3874f 100644 --- a/src/android/com/synconset/ImagePicker/ImagePicker.java +++ b/src/android/com/synconset/ImagePicker/ImagePicker.java @@ -11,17 +11,9 @@ import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; import android.app.Activity; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.net.Uri; import android.util.Log; public class ImagePicker extends CordovaPlugin { @@ -36,10 +28,25 @@ public class ImagePicker extends CordovaPlugin { if (action.equals("getPictures")) { Intent intent = new Intent(cordova.getActivity(), MultiImageChooserActivity.class); int max = 20; + int desiredWidth = 0; + int desiredHeight = 0; + int quality = 100; if (this.params.has("maximumImagesCount")) { max = this.params.getInt("maximumImagesCount"); } + if (this.params.has("width")) { + desiredWidth = this.params.getInt("width"); + } + if (this.params.has("height")) { + desiredWidth = this.params.getInt("height"); + } + if (this.params.has("quality")) { + quality = this.params.getInt("quality"); + } intent.putExtra("MAX_IMAGES", max); + intent.putExtra("WIDTH", desiredWidth); + intent.putExtra("HEIGHT", desiredHeight); + intent.putExtra("QUALITY", quality); if (this.cordova != null) { this.cordova.startActivityForResult((CordovaPlugin) this, intent, 0); } @@ -50,55 +57,8 @@ public class ImagePicker extends CordovaPlugin { public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { ArrayList fileNames = data.getStringArrayListExtra("MULTIPLEFILENAMES"); - JSONArray res = new JSONArray(); - try { - for (int i = 0; i < fileNames.size(); i++) { - File file = new File(fileNames.get(i)); - Bitmap bmp = this.getBitmap(file); - int width = bmp.getWidth(); - int height = bmp.getHeight(); - int desiredWidth = 0; - int desiredHeight = 0; - if (this.params.has("width")) { - desiredWidth = this.params.getInt("width"); - } - if (this.params.has("height")) { - desiredHeight = this.params.getInt("height"); - } - float widthScale = 1.0f; - float heightScale = 1.0f; - float scale = 1.0f; - if (desiredWidth > 0 || desiredHeight > 0) { - if (desiredHeight == 0 && desiredWidth < width) { - scale = (float)desiredWidth/width; - } else if (desiredWidth == 0 && desiredHeight < height) { - scale = (float)desiredHeight/height; - } else { - if (desiredWidth > 0 && desiredWidth < width) { - widthScale = (float)desiredWidth/width; - } - if (desiredHeight > 0 && desiredHeight < height) { - heightScale = (float)desiredHeight/height; - } - if (widthScale < heightScale) { - scale = widthScale; - } else { - scale = heightScale; - } - } - } - if (scale < 1) { - bmp = this.getResizedBitmap(bmp, scale); - } - file = this.storeImage(bmp, file.getName()); - res.put(Uri.fromFile(file).toString()); - } - this.callbackContext.success(res); - } catch(IOException e) { - this.callbackContext.error("There was an error importing pictures"); - } catch (JSONException e) { - this.callbackContext.error("There was an error importing pictures"); - } + JSONArray res = new JSONArray(fileNames); + this.callbackContext.success(res); } else if (resultCode == Activity.RESULT_CANCELED) { JSONArray res = new JSONArray(); this.callbackContext.success(res); @@ -106,55 +66,4 @@ public class ImagePicker extends CordovaPlugin { this.callbackContext.error("No images selected"); } } - - - /* - * The following functions are originally from - * https://github.com/raananw/PhoneGap-Image-Resizer - * - * They have been modified by Andrew Stephan for Sync OnSet - * - * The software is open source, MIT Licensed. - * Copyright (C) 2012, webXells GmbH All Rights Reserved. - */ - private File storeImage(Bitmap bmp, String fileName) throws JSONException, IOException { - int quality = 100; - if (this.params.has("quality")) { - quality = this.params.getInt("quality"); - } - int index = fileName.lastIndexOf('.'); - String name = fileName.substring(0, index); - String ext = fileName.substring(index); - File file = File.createTempFile(name, ext); - OutputStream outStream = new FileOutputStream(file); - if (ext.compareToIgnoreCase(".png") == 0) { - bmp.compress(Bitmap.CompressFormat.PNG, quality, outStream); - } else { - bmp.compress(Bitmap.CompressFormat.JPEG, quality, outStream); - } - outStream.flush(); - outStream.close(); - return file; - } - - private Bitmap getResizedBitmap(Bitmap bm, float factor) { - int width = bm.getWidth(); - int height = bm.getHeight(); - // create a matrix for the manipulation - Matrix matrix = new Matrix(); - // resize the bit map - matrix.postScale(factor, factor); - // recreate the new Bitmap - Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false); - return resizedBitmap; - } - - private Bitmap getBitmap(File file) throws IOException { - Bitmap bmp; - bmp = BitmapFactory.decodeFile(file.getAbsolutePath()); - if (bmp == null) { - throw new IOException("The image file could not be opened."); - } - return bmp; - } } \ No newline at end of file