diff --git a/app/AndroidManifest.xml b/app/AndroidManifest.xml index e38bc63..0dbfa02 100644 --- a/app/AndroidManifest.xml +++ b/app/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="14" + android:versionName="2.3" > fo = (FlowObjectToStringWrapper) data; Photo imageData = fo.getObject(); double ratio = imageData.getHeight() == 0 ? 1 : (float) imageData.getWidth() @@ -286,12 +303,10 @@ protected Bitmap processBitmap(Object data) int height = imageHeight; int width = (int) (height * ratio); Bitmap result = null; - try - { + try { imageData = PhotoUtils.validateUrlForSizeExistAndReturn(imageData, thumbSize); - result = super.processBitmap(fo.toString(), width, height); - } catch (Exception e) - { + result = super.processBitmap(fo.toString(), width, height, processingState); + } catch (Exception e) { GuiUtils.noAlertError(TAG, e); } return result; @@ -664,7 +679,7 @@ public void onScale(float scaleFactor, float focusX, float focusY) { "LibraryListOnTouchListener.onScale: new scale: %.2f, new height: %d", newScaleFactor, (int) (newScaleFactor * mImageThumbSize)); - if (newScaleFactor >= 0.5f && newScaleFactor <= 5.f) { + if (newScaleFactor >= 0.5f && newScaleFactor <= 5.f && mScaleFactor != newScaleFactor) { mScaleFactor = newScaleFactor; // Don't let the object get too small or too large. rebuildPhotosGrid(); diff --git a/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java b/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java index f737b62..c9a4e5d 100644 --- a/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java +++ b/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java @@ -869,10 +869,9 @@ public CustomImageFileSystemFetcher(Context context, } @Override - protected Bitmap processBitmap(Object data) - { + protected Bitmap processBitmap(Object data, ProcessingState processingState) { ImageData imageData = (ImageData) data; - return super.processBitmap(imageData.data); + return super.processBitmap(imageData.data, processingState); } } diff --git a/app/src/com/trovebox/android/app/bitmapfun/util/DiskLruCache.java b/app/src/com/trovebox/android/app/bitmapfun/util/DiskLruCache.java index fabf481..5751bb6 100644 --- a/app/src/com/trovebox/android/app/bitmapfun/util/DiskLruCache.java +++ b/app/src/com/trovebox/android/app/bitmapfun/util/DiskLruCache.java @@ -50,7 +50,7 @@ */ public class DiskLruCache { private static final String TAG = "DiskLruCache"; - private static final String CACHE_FILENAME_PREFIX = "cache_"; + public static final String CACHE_FILENAME_PREFIX = "cache_"; private static final int MAX_REMOVALS = 4; private static final int INITIAL_CAPACITY = 32; private static final float LOAD_FACTOR = 0.75f; @@ -440,6 +440,10 @@ public String createFilePath(String key) { return createFilePath(mCacheDir, key); } + public File getCacheDir() { + return mCacheDir; + } + /** * Sets the target compression format and quality for images written to the * disk cache. diff --git a/app/src/com/trovebox/android/app/bitmapfun/util/ImageFetcher.java b/app/src/com/trovebox/android/app/bitmapfun/util/ImageFetcher.java index 8ce3e41..94ee156 100644 --- a/app/src/com/trovebox/android/app/bitmapfun/util/ImageFetcher.java +++ b/app/src/com/trovebox/android/app/bitmapfun/util/ImageFetcher.java @@ -112,10 +112,12 @@ private void checkConnection(Context context) { * AsyncTaskEx background thread. * * @param data The data to load the bitmap, in this case, a regular http URL + * @param processingState may be used to determine whether the processing is + * cancelled during long operations * @return The downloaded and resized bitmap */ - private Bitmap processBitmap(String data) { - return processBitmap(data, imageWidth, imageHeight); + private Bitmap processBitmap(String data, ProcessingState processingState) { + return processBitmap(data, imageWidth, imageHeight, processingState); } /** @@ -125,15 +127,18 @@ private Bitmap processBitmap(String data) { * @param data The data to load the bitmap, in this case, a regular http URL * @param imageWidth * @param imageHeight + * @param processingState may be used to determine whether the processing is + * cancelled during long operations * @return The downloaded and resized bitmap */ - protected Bitmap processBitmap(String data, int imageWidth, int imageHeight) { + protected Bitmap processBitmap(String data, int imageWidth, int imageHeight, + ProcessingState processingState) { if (BuildConfig.DEBUG) { Log.d(TAG, "processBitmap - " + data); } // Download a bitmap, write it to a file - final File f = downloadBitmap(mContext, data, mCheckLoggedIn); + final File f = downloadBitmap(mContext, data, mCheckLoggedIn, processingState); if (f != null) { try @@ -151,8 +156,8 @@ protected Bitmap processBitmap(String data, int imageWidth, int imageHeight) { } @Override - protected Bitmap processBitmap(Object data) { - return processBitmap(String.valueOf(data)); + protected Bitmap processBitmap(Object data, ProcessingState processingState) { + return processBitmap(String.valueOf(data), processingState); } /** @@ -162,9 +167,12 @@ protected Bitmap processBitmap(Object data) { * @param context The context to use * @param urlString The URL to fetch * @param checkLoggedIn whether to check user logged in condition + * @param processingState may be used to determine whether the processing is + * cancelled during long operations * @return A File pointing to the fetched bitmap */ - public static File downloadBitmap(Context context, String urlString, boolean checkLoggedIn) { + public static File downloadBitmap(Context context, String urlString, boolean checkLoggedIn, + ProcessingState processingState) { final File cacheDir = DiskLruCache.getDiskCacheDir(context, HTTP_CACHE_DIR); if (CommonUtils.TEST_CASE && urlString == null) @@ -196,7 +204,9 @@ public static File downloadBitmap(Context context, String urlString, boolean che return null; } } - + if (processingState != null && processingState.isProcessingCancelled()) { + return null; + } final File cacheFile = new File(cache.createFilePath(urlString)); if (cache.containsKey(urlString)) { @@ -225,16 +235,42 @@ public static File downloadBitmap(Context context, String urlString, boolean che long start = System.currentTimeMillis(); final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); - final InputStream in = - new BufferedInputStream(urlConnection.getInputStream(), Utils.IO_BUFFER_SIZE); - out = new BufferedOutputStream(new FileOutputStream(cacheFile), Utils.IO_BUFFER_SIZE); + + final InputStream in = new BufferedInputStream(urlConnection.getInputStream(), + Utils.IO_BUFFER_SIZE); + File tempFile = File.createTempFile(DiskLruCache.CACHE_FILENAME_PREFIX + "udl" + + cacheFile.getName(), null, + cache.getCacheDir()); + out = new BufferedOutputStream(new FileOutputStream(tempFile), Utils.IO_BUFFER_SIZE); int b; while ((b = in.read()) != -1) { out.write(b); + if (processingState != null && processingState.isProcessingCancelled()) { + CommonUtils.debug(TAG, + "downloadBitmap: processing is cancelled. Removing temp file %1$s", + tempFile.getAbsolutePath()); + out.close(); + out = null; + tempFile.delete(); + return null; + } } TrackerUtils.trackDataLoadTiming(System.currentTimeMillis() - start, "downloadBitmap", TAG); + if (!cacheFile.exists()) { + CommonUtils.debug(TAG, + "downloadBitmap: cache file %1$s doesn't exist, renaming downloaded data", + cacheFile.getAbsolutePath()); + if (!tempFile.renameTo(cacheFile)) { + return null; + } + } else { + CommonUtils.debug(TAG, + "downloadBitmap: cache file %1$s exists, removing downloaded data", + cacheFile.getAbsolutePath()); + tempFile.delete(); + } return cacheFile; } catch (final IOException e) { diff --git a/app/src/com/trovebox/android/app/bitmapfun/util/ImageFileSystemFetcher.java b/app/src/com/trovebox/android/app/bitmapfun/util/ImageFileSystemFetcher.java index c0a94d6..c7f733b 100644 --- a/app/src/com/trovebox/android/app/bitmapfun/util/ImageFileSystemFetcher.java +++ b/app/src/com/trovebox/android/app/bitmapfun/util/ImageFileSystemFetcher.java @@ -101,7 +101,7 @@ public static Bitmap processBitmap(String fileName, int imageWidth, int imageHei } @Override - protected Bitmap processBitmap(Object data) + protected Bitmap processBitmap(Object data, ProcessingState processingState) { return processBitmap(String.valueOf(data)); } diff --git a/app/src/com/trovebox/android/app/bitmapfun/util/ImageResizer.java b/app/src/com/trovebox/android/app/bitmapfun/util/ImageResizer.java index 94bb8ca..c08ce91 100644 --- a/app/src/com/trovebox/android/app/bitmapfun/util/ImageResizer.java +++ b/app/src/com/trovebox/android/app/bitmapfun/util/ImageResizer.java @@ -151,7 +151,7 @@ protected Bitmap processBitmap(int resId, int imageWidth, int imageHeight) { } @Override - protected Bitmap processBitmap(Object data) { + protected Bitmap processBitmap(Object data, ProcessingState processingState) { return processBitmap(Integer.parseInt(String.valueOf(data))); } diff --git a/app/src/com/trovebox/android/app/bitmapfun/util/ImageWorker.java b/app/src/com/trovebox/android/app/bitmapfun/util/ImageWorker.java index 8dfac71..2eae95a 100644 --- a/app/src/com/trovebox/android/app/bitmapfun/util/ImageWorker.java +++ b/app/src/com/trovebox/android/app/bitmapfun/util/ImageWorker.java @@ -271,6 +271,7 @@ public void setImageFadeIn(boolean fadeIn) { } public void setExitTasksEarly(boolean exitTasksEarly) { + CommonUtils.debug(TAG, "setExitTasksEarly: called for parameter %1$b", exitTasksEarly); mExitTasksEarly = exitTasksEarly; } @@ -282,9 +283,12 @@ public void setExitTasksEarly(boolean exitTasksEarly) { * * @param data The data to identify which image to process, as provided by * {@link ImageWorker#loadImage(Object, ImageView)} + * @param processingCancelledState may be used to determine whether the + * processing is cancelled during long operations * @return The processed bitmap */ - protected abstract Bitmap processBitmap(Object data); + protected abstract Bitmap processBitmap(Object data, + ProcessingState processingCancelledState); public static void cancelWork(ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); @@ -305,12 +309,13 @@ public static void cancelWork(ImageView imageView) { public static boolean cancelPotentialWork(Object data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - if (bitmapWorkerTask != null) { + if (bitmapWorkerTask != null && !bitmapWorkerTask.isCancelled()) { final Object bitmapData = bitmapWorkerTask.data; if (bitmapData == null || !bitmapData.equals(data)) { bitmapWorkerTask.cancel(true); if (BuildConfig.DEBUG) { - CommonUtils.debug(TAG, "cancelPotentialWork - cancelled work for " + data); + CommonUtils + .debug(TAG, "cancelPotentialWork - cancelled work for " + bitmapData); } } else { // The same work is already in progress. @@ -339,7 +344,8 @@ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { /** * The actual AsyncTaskEx that will asynchronously process the image. */ - private class BitmapWorkerTask extends AsyncTaskEx { + private class BitmapWorkerTask extends AsyncTaskEx implements + ProcessingState { private Object data; private final WeakReference imageViewReference; LoadingControl loadingControl; @@ -419,7 +425,7 @@ protected Bitmap doInBackground(Object... params) { // process method (as implemented by a subclass) if (bitmap == null && !isCancelled() && getAttachedImageView() != null && !mExitTasksEarly) { - bitmap = processBitmap(params[0]); + bitmap = processBitmap(params[0], this); } // If the bitmap was processed and the image cache is available, @@ -476,6 +482,11 @@ private ImageView getAttachedImageView() { return null; } + + @Override + public boolean isProcessingCancelled() { + return isCancelled() || mExitTasksEarly; + } } /** @@ -567,6 +578,15 @@ public ImageWorkerAdapter getAdapter() { return mImageWorkerAdapter; } + /** + * An interface to handle processing cancelled state in the processBitmap + * method. Useful for long loading operations such as downloadBitmap in + * ImageFetcher + */ + public static interface ProcessingState { + boolean isProcessingCancelled(); + } + /** * A very simple adapter for use with ImageWorker class and subclasses. */ diff --git a/app/src/com/trovebox/android/app/util/ImageFlowUtils.java b/app/src/com/trovebox/android/app/util/ImageFlowUtils.java index 69a49d8..520c41c 100644 --- a/app/src/com/trovebox/android/app/util/ImageFlowUtils.java +++ b/app/src/com/trovebox/android/app/util/ImageFlowUtils.java @@ -13,6 +13,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.LinearLayout.LayoutParams; import com.trovebox.android.app.bitmapfun.util.ImageWorker; @@ -313,6 +314,7 @@ public View getView( int imageViewId, Context context) { ViewGroup view; + CommonUtils.debug(TAG, "getView: called for position %1$d", position); if (convertView == null) { // if it's not recycled, instantiate and initialize final LayoutInflater layoutInflater = (LayoutInflater) @@ -433,17 +435,20 @@ protected View getSingleImageView( int height = imageHeight; int width = (int) (ratio * height) + extraWidth; - CommonUtils.debug(TAG, "Processing image: " + value + CommonUtils.debug(TAG, "getSingleImageView: processing image: " + value + "; Width: " + getWidth(value) + "; Height: " + getHeight(value) + "; Extra width: " + extraWidth + "; Req Width: " + width + "; Req Height: " + height); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - width + 2 * borderSize, - height + 2 * borderSize); - view.setLayoutParams(layoutParams); + width = width + 2 * borderSize; + height = height + 2 * borderSize; + LinearLayout.LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + if (layoutParams == null || layoutParams.width != width || layoutParams.height != height) { + layoutParams = new LinearLayout.LayoutParams(width, height); + view.setLayoutParams(layoutParams); + } additionalSingleImageViewInit(view, value); ImageView imageView = (ImageView) view.findViewById(imageViewId);