diff --git a/astrid/src/main/java/com/todoroo/astrid/actfm/CommentsFragment.java b/astrid/src/main/java/com/todoroo/astrid/actfm/CommentsFragment.java index fdec84854..1127bea29 100644 --- a/astrid/src/main/java/com/todoroo/astrid/actfm/CommentsFragment.java +++ b/astrid/src/main/java/com/todoroo/astrid/actfm/CommentsFragment.java @@ -46,13 +46,10 @@ import com.todoroo.astrid.adapter.UpdateAdapter; import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.UserActivity; -import com.todoroo.astrid.helper.AsyncImageView; import org.json.JSONObject; import org.tasks.R; -import edu.mit.mobile.android.imagecache.ImageCache; - public abstract class CommentsFragment extends SherlockListFragment { // private TagData tagData; @@ -72,15 +69,12 @@ public abstract class CommentsFragment extends SherlockListFragment { protected static final int MENU_REFRESH_ID = Menu.FIRST; - protected final ImageCache imageCache; - protected Resources resources; @Autowired UserActivityDao userActivityDao; public CommentsFragment() { DependencyInjectionService.getInstance().inject(this); - imageCache = AsyncImageView.getImageCache(); } @Override 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 5943c6bbe..2b9afca36 100644 --- a/astrid/src/main/java/com/todoroo/astrid/actfm/TagSettingsActivity.java +++ b/astrid/src/main/java/com/todoroo/astrid/actfm/TagSettingsActivity.java @@ -39,7 +39,6 @@ import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.TagMetadata; import com.todoroo.astrid.data.User; -import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.ThemeService; @@ -53,8 +52,6 @@ import org.json.JSONException; import org.json.JSONObject; import org.tasks.R; -import edu.mit.mobile.android.imagecache.ImageCache; - public class TagSettingsActivity extends SherlockFragmentActivity { public static final String TOKEN_NEW_FILTER = "newFilter"; //$NON-NLS-1$ @@ -82,14 +79,12 @@ public class TagSettingsActivity extends SherlockFragmentActivity { private EditText tagName; private Bitmap setBitmap; - private final ImageCache imageCache; private boolean isNewTag = false; private boolean isDialog; public TagSettingsActivity() { DependencyInjectionService.getInstance().inject(this); - imageCache = AsyncImageView.getImageCache(); } @Override @@ -269,7 +264,6 @@ public class TagSettingsActivity extends SherlockFragmentActivity { } try { String tagPicture = RemoteModel.PictureHelper.getPictureHash(tagData); - imageCache.put(tagPicture, bitmap); tagData.setValue(TagData.PICTURE, tagPicture); } catch (Exception e) { 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 fd3f085c0..b3e52fe2b 100644 --- a/astrid/src/main/java/com/todoroo/astrid/adapter/UpdateAdapter.java +++ b/astrid/src/main/java/com/todoroo/astrid/adapter/UpdateAdapter.java @@ -23,6 +23,7 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.CursorAdapter; +import android.widget.ImageView; import android.widget.TextView; import com.todoroo.andlib.data.Property; @@ -39,14 +40,9 @@ import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.UserActivity; -import com.todoroo.astrid.helper.AsyncImageView; import org.tasks.R; -import java.io.IOException; - -import edu.mit.mobile.android.imagecache.ImageCache; - /** * Adapter for displaying a user's activity * @@ -60,7 +56,6 @@ public class UpdateAdapter extends CursorAdapter { protected final Fragment fragment; private final int resource; private final LayoutInflater inflater; - private final ImageCache imageCache; private final String linkColor; private final String fromView; @@ -138,7 +133,6 @@ public class UpdateAdapter extends CursorAdapter { inflater = (LayoutInflater) fragment.getActivity().getSystemService( Context.LAYOUT_INFLATER_SERVICE); - imageCache = AsyncImageView.getImageCache(); this.fromView = fromView; this.resource = resource; @@ -269,7 +263,7 @@ public class UpdateAdapter extends CursorAdapter { } private void setupUserActivityRow(View view, UserActivity activity, User user) { - final AsyncImageView commentPictureView = (AsyncImageView)view.findViewById(R.id.comment_picture); { + final ImageView commentPictureView = (ImageView)view.findViewById(R.id.comment_picture); { String pictureThumb = activity.getPictureUrl(UserActivity.PICTURE, RemoteModel.PICTURE_MEDIUM); String pictureFull = activity.getPictureUrl(UserActivity.PICTURE, RemoteModel.PICTURE_LARGE); Bitmap updateBitmap = null; @@ -277,7 +271,7 @@ public class UpdateAdapter extends CursorAdapter { updateBitmap = activity.getPictureBitmap(UserActivity.PICTURE); } setupImagePopupForCommentView(view, commentPictureView, pictureThumb, pictureFull, updateBitmap, - activity.getValue(UserActivity.MESSAGE), fragment, imageCache); + activity.getValue(UserActivity.MESSAGE), fragment); } // name @@ -302,37 +296,23 @@ public class UpdateAdapter extends CursorAdapter { return false; } - public static void setupImagePopupForCommentView(View view, AsyncImageView commentPictureView, final String pictureThumb, final String pictureFull, final Bitmap updateBitmap, - final String message, final Fragment fragment, ImageCache imageCache) { + public static void setupImagePopupForCommentView(View view, ImageView commentPictureView, final String pictureThumb, final String pictureFull, final Bitmap updateBitmap, + final String message, final Fragment fragment) { if ((!TextUtils.isEmpty(pictureThumb) && !"null".equals(pictureThumb)) || updateBitmap != null) { //$NON-NLS-1$ commentPictureView.setVisibility(View.VISIBLE); if (updateBitmap != null) { commentPictureView.setImageBitmap(updateBitmap); - } else { - commentPictureView.setUrl(pictureThumb); - } - - if (pictureThumb != null && imageCache.contains(pictureThumb) && updateBitmap == null) { - try { - commentPictureView.setImageBitmap(imageCache.get(pictureThumb)); - } catch (IOException e) { - e.printStackTrace(); - } - } else if (updateBitmap == null) { - commentPictureView.setUrl(pictureThumb); } view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { AlertDialog image = new AlertDialog.Builder(fragment.getActivity()).create(); - AsyncImageView imageView = new AsyncImageView(fragment.getActivity()); + ImageView imageView = new ImageView(fragment.getActivity()); imageView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); imageView.setImageResource(android.R.drawable.ic_menu_gallery); if (updateBitmap != null) { imageView.setImageBitmap(updateBitmap); - } else { - imageView.setUrl(pictureFull); } image.setView(imageView); diff --git a/astrid/src/main/java/com/todoroo/astrid/helper/AsyncImageView.java b/astrid/src/main/java/com/todoroo/astrid/helper/AsyncImageView.java deleted file mode 100644 index cb8840755..000000000 --- a/astrid/src/main/java/com/todoroo/astrid/helper/AsyncImageView.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.helper; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.os.Looper; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.todoroo.andlib.service.ContextManager; - -import java.io.IOException; - -import edu.mit.mobile.android.imagecache.ImageCache; - -public class AsyncImageView extends ImageView { - - private final ImageCache imageDiskCache; - private Bitmap cacheImage; - private String cacheURL = ""; //$NON-NLS-1$ - public AsyncImageView(Context context) { - super(context); - imageDiskCache = getImageCache(); - } - public AsyncImageView(Context context, AttributeSet set) { - super(context, set); - imageDiskCache = getImageCache(); - } - public AsyncImageView(Context context, AttributeSet set, int defStyle) { - super(context, set, defStyle); - imageDiskCache = getImageCache(); - } - - public void setUrl(String url) { - if (cacheImage != null && cacheURL.equals(url) && !TextUtils.isEmpty(url)) { - setImageBitmap(cacheImage); - return; - } else if (url != null && imageDiskCache != null && imageDiskCache.contains(url)) { - try { - cacheImage = imageDiskCache.get(url); - setImageBitmap(cacheImage); - cacheURL = url; - return; - } catch (IOException e) { - // - } - } - } - - private static volatile ImageCache imageCacheInstance = null; - - public static ImageCache getImageCache() { - if (imageCacheInstance == null) { - synchronized(AsyncImageView.class) { - if (imageCacheInstance == null) { - try { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - } catch (Exception e) { - // Ignore - } - imageCacheInstance = new ImageCache(ContextManager.getContext(), CompressFormat.JPEG, 85); - } - } - } - return imageCacheInstance; - } -} 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 bf21cb1dc..a5ae951ad 100644 --- a/astrid/src/main/java/com/todoroo/astrid/notes/EditNoteActivity.java +++ b/astrid/src/main/java/com/todoroo/astrid/notes/EditNoteActivity.java @@ -27,6 +27,7 @@ import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -54,7 +55,6 @@ import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.UserActivity; -import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.TaskService; @@ -70,8 +70,6 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; -import edu.mit.mobile.android.imagecache.ImageCache; - public class EditNoteActivity extends LinearLayout implements TimerActionListener { public static final String EXTRA_TASK_ID = "task"; //$NON-NLS-1$ @@ -97,7 +95,6 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene private final Resources resources; - private final ImageCache imageCache; private final int cameraButton; private final String linkColor; private int historyCount = 0; @@ -118,7 +115,6 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene super(fragment.getActivity()); DependencyInjectionService.getInstance().inject(this); - imageCache = AsyncImageView.getImageCache(); this.fragment = fragment; this.activity = (AstridActivity) fragment.getActivity(); @@ -395,9 +391,8 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene } // picture - final AsyncImageView commentPictureView = (AsyncImageView)view.findViewById(R.id.comment_picture); { - UpdateAdapter.setupImagePopupForCommentView(view, commentPictureView, item.pictureThumb, item.pictureFull, item.commentBitmap, item.title.toString(), fragment, imageCache); - } + final ImageView commentPictureView = (ImageView)view.findViewById(R.id.comment_picture); + UpdateAdapter.setupImagePopupForCommentView(view, commentPictureView, item.pictureThumb, item.pictureFull, item.commentBitmap, item.title.toString(), fragment); } public void refreshData() { diff --git a/astrid/src/main/java/edu/mit/mobile/android/imagecache/DiskCache.java b/astrid/src/main/java/edu/mit/mobile/android/imagecache/DiskCache.java deleted file mode 100644 index d2cf7bb4b..000000000 --- a/astrid/src/main/java/edu/mit/mobile/android/imagecache/DiskCache.java +++ /dev/null @@ -1,604 +0,0 @@ -package edu.mit.mobile.android.imagecache; - -/* - * Copyright (C) 2011-2013 MIT Mobile Experience Lab - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -import android.os.Build; -import android.os.StatFs; -import android.util.Log; - -import com.todoroo.astrid.utility.Constants; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - *

- * A simple disk cache. - *

- * - *

- * By default, the maximum size of the cache is automatically set based on the amount of free space - * available to the cache. Alternatively, a fixed size can be specified using - * {@link #setCacheMaxSize(long)}. - *

- * - *

- * By default, the cache will automatically maintain its size by periodically checking to see if it - * estimates that a trim is needed and if it is, proceeding to running {@link #trim()} on a worker - * thread. This feature can be controlled by {@link #setAutoTrimFrequency(int)}. - *

- * - * @author Steve Pomeroy - * - * @param - * the key to store/retrieve the value - * @param - * the value that will be stored to disk - */ -public abstract class DiskCache { - private static final String TAG = "DiskCache"; - - /** - * Automatically determines the maximum size of the cache based on available free space. - */ - public static final int AUTO_MAX_CACHE_SIZE = 0; - - /** - * The default number of cache hits before {@link #trim()} is automatically triggered. See - * {@link #setAutoTrimFrequency(int)}. - */ - public static final int DEFAULT_AUTO_TRIM_FREQUENCY = 10; - - /** - * Pass to {@link #setAutoTrimFrequency(int)} to disable automatic trimming. See {@link #trim()} - * . - */ - public static final int AUTO_TRIM_DISABLED = 0; - - // ///////////////////////////////////////////// - - private long mMaxDiskUsage = AUTO_MAX_CACHE_SIZE; - - private MessageDigest hash; - - private final File mCacheBase; - private final String mCachePrefix, mCacheSuffix; - - private final ConcurrentLinkedQueue mQueue = new ConcurrentLinkedQueue(); - - /** - * In auto max cache mode, the maximum is set to the total free space divided by this amount. - */ - private static final int AUTO_MAX_CACHE_SIZE_DIVISOR = 10; - - private int mAutoTrimFrequency = DEFAULT_AUTO_TRIM_FREQUENCY; - - private final ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue()); - - private int mAutoTrimHitCount = 1; - - private long mEstimatedDiskUsage; - - private long mEstimatedFreeSpace; - - /** - * Creates a new disk cache with no cachePrefix or cacheSuffix - */ - public DiskCache(File cacheBase) { - this(cacheBase, null, null); - } - - /** - * Creates a new disk cache. - * - * @param cacheBase - * The base directory within which all the cache files will be stored. - * @param cachePrefix - * If you want a prefix to the filenames, place one here. Otherwise, pass null. - * @param cacheSuffix - * A suffix to the cache filename. Null is also ok here. - */ - public DiskCache(File cacheBase, String cachePrefix, String cacheSuffix) { - mCacheBase = cacheBase; - mCachePrefix = cachePrefix; - mCacheSuffix = cacheSuffix; - - try { - hash = MessageDigest.getInstance("SHA-1"); - - } catch (final NoSuchAlgorithmException e) { - try { - hash = MessageDigest.getInstance("MD5"); - } catch (final NoSuchAlgorithmException e2) { - final RuntimeException re = new RuntimeException("No available hashing algorithm"); - re.initCause(e2); - throw re; - } - } - - updateDiskUsageInBg(); - } - - /** - * Sets the maximum size of the cache, in bytes. The default is to automatically manage the max - * size based on the available disk space. This can be explicitly set by passing this - * {@link #AUTO_MAX_CACHE_SIZE}. - * - * @param maxSize - * maximum size of the cache, in bytes. - */ - public void setCacheMaxSize(long maxSize) { - mMaxDiskUsage = maxSize; - } - - /** - * After this many puts, if it looks like there's a low space condition, {@link #trim()} will - * automatically be called. - * - * @param autoTrimFrequency - * Set to {@link #AUTO_TRIM_DISABLED} to turn off auto trim. The default is - * {@link #DEFAULT_AUTO_TRIM_FREQUENCY}. - */ - public void setAutoTrimFrequency(int autoTrimFrequency) { - mAutoTrimFrequency = autoTrimFrequency; - } - - /** - * Updates cached estimates on the - */ - private void updateDiskUsageEstimates() { - final long diskUsage = getCacheDiskUsage(); - - final long availableSpace = getFreeSpace(); - - synchronized (this) { - mEstimatedDiskUsage = diskUsage; - mEstimatedFreeSpace = availableSpace; - } - } - - private void updateDiskUsageInBg() { - mExecutor.execute(new Runnable() { - - @Override - public void run() { - updateDiskUsageEstimates(); - } - }); - } - - /** - * Gets the amount of space free on the cache volume. - * - * @return free space in bytes. - */ - private long getFreeSpace() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - return mCacheBase.getUsableSpace(); - } else { - // maybe make singleton - final StatFs stat = new StatFs(mCacheBase.getAbsolutePath()); - return (long) stat.getAvailableBlocks() * (long) stat.getBlockSize(); - } - } - - /** - * Gets the cache filename for the given key. - */ - protected File getFile(K key) { - return new File(mCacheBase, (mCachePrefix != null ? mCachePrefix : "") + hash(key) - + (mCacheSuffix != null ? mCacheSuffix : "")); - } - - /** - * Writes the value stored in the cache to disk by calling - * {@link #toDisk(Object, Object, OutputStream)}. - * - * @param key - * The key to find the value. - * @param value - * the data to be written to disk. - */ - public final synchronized void put(K key, V value) throws IOException, FileNotFoundException { - final File saveHere = getFile(key); - - final OutputStream os = new FileOutputStream(saveHere); - toDisk(key, value, os); - os.close(); - - mEstimatedDiskUsage += saveHere.length(); - - touchEntry(saveHere); - - autotrim(); - } - - /** - * Writes the contents of the InputStream straight to disk. It is the caller's responsibility to - * ensure it's the same type as what would be written with - * {@link #toDisk(Object, Object, OutputStream)} - * @throws IOException - * @throws FileNotFoundException - */ - public final void putRaw(K key, InputStream value) throws IOException, FileNotFoundException { - - final File saveHere = getFile(key); - - final File tempFile = new File(saveHere.getAbsolutePath() + ".temp"); - - boolean allGood = false; - try { - final OutputStream os = new FileOutputStream(tempFile); - - inputStreamToOutputStream(value, os); - os.close(); - - synchronized (this) { - // overwrite - saveHere.delete(); - tempFile.renameTo(saveHere); - } - allGood = true; - } finally { - // clean up on any exception - if (!allGood) { - saveHere.delete(); - tempFile.delete(); - } - } - if (allGood) { - mEstimatedDiskUsage += saveHere.length(); - - touchEntry(saveHere); - - autotrim(); - } - } - - /** - * Puts the key at the end of the queue, removing it if it's already present. This will cause it - * to be removed last when {@link #trim()} is called. - */ - private void touchEntry(File cacheFile) { - if (mQueue.contains(cacheFile)) { - mQueue.remove(cacheFile); - } - mQueue.add(cacheFile); - } - - /** - * Marks the given key as accessed recently. This will deprioritize it from automatically being - * purged upon {@link #trim()}. - */ - protected void touchKey(K key) { - touchEntry(getFile(key)); - } - - /** - * Call this every time you may be able to start a trim in the background. This implicitly runs - * {@link #updateDiskUsageInBg()} each time it's called. - */ - private void autotrim() { - if (mAutoTrimFrequency == 0) { - return; - } - - mAutoTrimHitCount = (mAutoTrimHitCount + 1) % mAutoTrimFrequency; - - if (mAutoTrimHitCount == 0 - && mEstimatedDiskUsage > Math.min(mEstimatedFreeSpace, mMaxDiskUsage)) { - - mExecutor.execute(new Runnable() { - @Override - public void run() { - trim(); - } - }); - } - - updateDiskUsageInBg(); - } - - /** - * Reads from an inputstream, dumps to an outputstream - * @throws IOException - */ - static public void inputStreamToOutputStream(InputStream is, OutputStream os) - throws IOException { - final int bufsize = 8196 * 10; - final byte[] cbuf = new byte[bufsize]; - - for (int readBytes = is.read(cbuf, 0, bufsize); readBytes > 0; readBytes = is.read(cbuf, 0, - bufsize)) { - os.write(cbuf, 0, readBytes); - } - } - - /** - * Reads the value from disk using {@link #fromDisk(Object, InputStream)}. - * - * @return The value for key or null if the key doesn't map to any existing entries. - */ - public final synchronized V get(K key) throws IOException { - final File readFrom = getFile(key); - - if (!readFrom.exists()) { - return null; - } - - final InputStream is = new FileInputStream(readFrom); - final V out = fromDisk(key, is); - is.close(); - - touchEntry(readFrom); - - return out; - } - - /** - * Checks the disk cache for a given key. - * - * @return true if the disk cache contains the given key - */ - public final synchronized boolean contains(K key) { - final File readFrom = getFile(key); - - return readFrom.exists(); - } - - /** - * Removes the item from the disk cache. - * - * @return true if the cached item has been removed or was already removed, false if it was not - * able to be removed. - */ - public synchronized boolean clear(K key) { - final File readFrom = getFile(key); - - if (!readFrom.exists()) { - return true; - } - final long size = readFrom.length(); - - final boolean success = readFrom.delete(); - - if (success) { - mEstimatedDiskUsage -= size; - } - - return success; - } - - /** - * Removes the item from the disk cache. - * - * @return true if the cached item has been removed or was already removed, false if it was not - * able to be removed. - */ - private synchronized boolean clear(File cacheFile) { - - if (!cacheFile.exists()) { - return true; - } - final long size = cacheFile.length(); - - final boolean success = cacheFile.delete(); - - if (success) { - mEstimatedDiskUsage -= size; - } - - return success; - } - - /** - * Clears the cache files from disk. - * - * Note: this only clears files that match the given prefix/suffix. - * - * @return true if the operation succeeded without error. It is possible that it will fail and - * the cache ends up being partially cleared. - */ - public synchronized boolean clear() { - boolean success = true; - - for (final File cacheFile : mCacheBase.listFiles(mCacheFileFilter)) { - if (!cacheFile.delete()) { - Log.e(TAG, "error deleting " + cacheFile); - success = false; - } - } - return success; - } - - /** - * @return the number of files in the cache - * @deprecated please use {@link #getCacheEntryCount()} or {@link #getCacheDiskUsage()} instead. - */ - @Deprecated - public int getCacheSize() { - return getCacheEntryCount(); - } - - /** - * @return the number of files in the cache as it is on disk. - */ - public int getCacheEntryCount() { - return mCacheBase.listFiles(mCacheFileFilter).length; - } - - /** - * @return the size of the cache in bytes, as it is on disk. - */ - public long getCacheDiskUsage() { - long usage = 0; - for (final File cacheFile : mCacheBase.listFiles(mCacheFileFilter)) { - usage += cacheFile.length(); - } - return usage; - } - - private final CacheFileFilter mCacheFileFilter = new CacheFileFilter(); - - private class CacheFileFilter implements FileFilter { - @Override - public boolean accept(File pathname) { - final String path = pathname.getName(); - return (mCachePrefix != null ? path.startsWith(mCachePrefix) : true) - && (mCacheSuffix != null ? path.endsWith(mCacheSuffix) : true); - } - }; - - private final Comparator mLastModifiedOldestFirstComparator = new Comparator() { - - @Override - public int compare(File lhs, File rhs) { - return Long.valueOf(lhs.lastModified()).compareTo(rhs.lastModified()); - } - }; - - /** - * Clears out cache entries in order to reduce the on-disk usage to the desired maximum size. - * This is a somewhat expensive operation, so it should be done on a background thread. - * - * @return the number of bytes worth of files that were trimmed. - * @see #setCacheMaxSize(long) - */ - public synchronized long trim() { - - long desiredSize; - final long freeSpace = getFreeSpace(); - - if (mMaxDiskUsage > 0) { - desiredSize = mMaxDiskUsage; - } else { - desiredSize = getFreeSpace() / AUTO_MAX_CACHE_SIZE_DIVISOR; - } - - desiredSize = Math.min(freeSpace, desiredSize); - - final long sizeToTrim = Math.max(0, getCacheDiskUsage() - desiredSize); - - if (sizeToTrim == 0) { - return 0; - } - - long trimmed = 0; - - final List sorted = Arrays.asList(mCacheBase.listFiles(mCacheFileFilter)); - Collections.sort(sorted, mLastModifiedOldestFirstComparator); - - // first clear out any files that aren't in the queue - for (final File cacheFile : sorted) { - if (mQueue.contains(cacheFile)) { - continue; - } - - final long size = cacheFile.length(); - if (clear(cacheFile)) { - trimmed += size; - if (Constants.DEBUG) { - Log.d(TAG, "trimmed unqueued " + cacheFile.getName() + " from cache."); - } - } - - if (trimmed >= sizeToTrim) { - break; - } - } - - while (trimmed < sizeToTrim && !mQueue.isEmpty()) { - final File cacheFile = mQueue.poll(); - - // shouldn't happen due to the check above, but just in case... - if (cacheFile == null) { - break; - } - - final long size = cacheFile.length(); - - if (clear(cacheFile)) { - trimmed += size; - if (Constants.DEBUG) { - Log.d(TAG, "trimmed " + cacheFile.getName() + " from cache."); - } - } else { - Log.e(TAG, "error deleting " + cacheFile); - } - } - - if (Constants.DEBUG) { - Log.d(TAG, "trimmed a total of " + trimmed + " bytes from cache."); - } - return trimmed; - } - - /** - * Implement this to do the actual disk writing. Do not close the OutputStream; it will be - * closed for you. - */ - protected abstract void toDisk(K key, V in, OutputStream out); - - /** - * Implement this to do the actual disk reading. - * @return a new instance of {@link V} containing the contents of in. - */ - protected abstract V fromDisk(K key, InputStream in); - - /** - * Using the key's {@link Object#toString() toString()} method, generates a string suitable for - * using as a filename. - * - * @return a string uniquely representing the the key. - */ - public String hash(K key) { - final byte[] ba; - - // MessageDigest isn't threadsafe, so we need to ensure it doesn't tread on itself. - synchronized (hash) { - hash.update(key.toString().getBytes()); - ba = hash.digest(); - } - final BigInteger bi = new BigInteger(1, ba); - final String result = bi.toString(16); - if (result.length() % 2 != 0) { - return "0" + result; - } - return result; - - } -} diff --git a/astrid/src/main/java/edu/mit/mobile/android/imagecache/DrawableMemCache.java b/astrid/src/main/java/edu/mit/mobile/android/imagecache/DrawableMemCache.java deleted file mode 100644 index f9fc71b19..000000000 --- a/astrid/src/main/java/edu/mit/mobile/android/imagecache/DrawableMemCache.java +++ /dev/null @@ -1,44 +0,0 @@ -package edu.mit.mobile.android.imagecache; - -/* - * Copyright (C) 2012 MIT Mobile Experience Lab - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.support.v4.util.LruCache; - -public class DrawableMemCache extends LruCache { - - private static final String TAG = DrawableMemCache.class.getSimpleName(); - - public DrawableMemCache(int maxSize) { - super(maxSize); - } - - @Override - protected int sizeOf(T key, Drawable value) { - int size = 0; - if (value instanceof BitmapDrawable) { - final Bitmap b = ((BitmapDrawable) value).getBitmap(); - if (b != null) { - size = b.getRowBytes() * b.getHeight(); - } - } - return size; - } -} diff --git a/astrid/src/main/java/edu/mit/mobile/android/imagecache/ImageCache.java b/astrid/src/main/java/edu/mit/mobile/android/imagecache/ImageCache.java deleted file mode 100644 index 82bca12d5..000000000 --- a/astrid/src/main/java/edu/mit/mobile/android/imagecache/ImageCache.java +++ /dev/null @@ -1,810 +0,0 @@ -package edu.mit.mobile.android.imagecache; - -/* - * Copyright (C) 2011-2012 MIT Mobile Experience Lab - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -import android.app.Activity; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import android.util.SparseArray; -import android.widget.ImageView; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.HttpResponseException; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.HttpParams; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLConnection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - *

- * An image download-and-cacher that also knows how to efficiently generate thumbnails of various - * sizes. - *

- * - *

- * The cache is shared with the entire process, so make sure you - * {@link #registerOnImageLoadListener(OnImageLoadListener)} and - * {@link #unregisterOnImageLoadListener(OnImageLoadListener)} any load listeners in your - * activities. - *

- * - * @author Steve Pomeroy - * - */ -public class ImageCache extends DiskCache { - private static final String TAG = ImageCache.class.getSimpleName(); - - static final boolean DEBUG = false; - - // whether to use Apache HttpClient or URL.openConnection() - private static final boolean USE_APACHE_NC = true; - - // the below settings are copied from AsyncTask.java - private static final int CORE_POOL_SIZE = 5; // thread - private static final int MAXIMUM_POOL_SIZE = 128; // thread - private static final int KEEP_ALIVE_TIME = 1; // second - - private final HashSet mImageLoadListeners = new HashSet(); - - public static final int DEFAULT_CACHE_SIZE = (24 /* MiB */* 1024 * 1024); // in bytes - - private DrawableMemCache mMemCache = new DrawableMemCache(DEFAULT_CACHE_SIZE); - - private Integer mIDCounter = 0; - - private static ImageCache mInstance; - - // this is a custom Executor, as we want to have the tasks loaded in FILO order. FILO works - // particularly well when scrolling with a ListView. - private final ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, - MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, - new PriorityBlockingQueue()); - - // ignored as SparseArray isn't thread-safe - private final Map jobs = Collections - .synchronizedMap(new HashMap()); - - private final HttpClient hc; - - private CompressFormat mCompressFormat; - private int mQuality; - - private final Resources mRes; - - private static final int MSG_IMAGE_LOADED = 100; - - private final KeyedLock mDownloading = new KeyedLock(); - - private static class ImageLoadHandler extends Handler { - private final ImageCache mCache; - - public ImageLoadHandler(ImageCache cache) { - super(); - mCache = cache; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_IMAGE_LOADED: - mCache.notifyListeners((LoadResult) msg.obj); - break; - } - }; - } - - private final ImageLoadHandler mHandler = new ImageLoadHandler(this); - - // TODO make it so this is customizable on the instance level. - /** - * Gets an instance of the cache. - * - * @return an instance of the cache - */ - public static ImageCache getInstance(Context context) { - if (mInstance == null) { - mInstance = new ImageCache(context, CompressFormat.JPEG, 85); - } - return mInstance; - } - - /** - * Generally, it's best to use the shared image cache using {@link #getInstance(Context)}. Use - * this if you want to customize a cache or keep it separate. - */ - public ImageCache(Context context, CompressFormat format, int quality) { - super(context.getCacheDir(), null, getExtension(format)); - if (USE_APACHE_NC) { - hc = getHttpClient(); - } else { - hc = null; - } - - mRes = context.getResources(); - - mCompressFormat = format; - mQuality = quality; - } - - /** - * Sets the compression format for resized images. - */ - public void setCompressFormat(CompressFormat format) { - mCompressFormat = format; - } - - /** - * Set the image quality. Hint to the compressor, 0-100. 0 meaning compress for small size, 100 - * meaning compress for max quality. Some formats, like PNG which is lossless, will ignore the - * quality setting - */ - public void setQuality(int quality) { - mQuality = quality; - } - - /** - * Sets the maximum size of the memory cache. Note, this will clear the memory cache. - * - * @param maxSize - * the maximum size of the memory cache in bytes. - */ - public void setMemCacheMaxSize(int maxSize) { - mMemCache = new DrawableMemCache(maxSize); - } - - private static String getExtension(CompressFormat format) { - String extension; - switch (format) { - case JPEG: - extension = ".jpg"; - break; - - case PNG: - extension = ".png"; - break; - - default: - throw new IllegalArgumentException(); - } - - return extension; - } - - /** - * If loading a number of images where you don't have a unique ID to represent the individual - * load, this can be used to generate a sequential ID. - * - * @return a new unique ID - */ - public int getNewID() { - synchronized (mIDCounter) { - return mIDCounter++; - } - } - - @Override - protected Bitmap fromDisk(String key, InputStream in) { - - if (DEBUG) { - Log.d(TAG, "disk cache hit for key " + key); - } - try { - final Bitmap image = BitmapFactory.decodeStream(in); - return image; - - } catch (final OutOfMemoryError oom) { - oomClear(); - return null; - } - } - - @Override - protected void toDisk(String key, Bitmap image, OutputStream out) { - if (DEBUG) { - Log.d(TAG, "disk cache write for key " + key); - } - if (image != null) { - if (!image.compress(mCompressFormat, mQuality, out)) { - Log.e(TAG, "error writing compressed image to disk for key " + key); - } - } else { - Log.e(TAG, "Ignoring attempt to write null image to disk cache"); - } - } - - /** - * Gets an instance of AndroidHttpClient if the devices has it (it was introduced in 2.2), or - * falls back on a http client that should work reasonably well. - * - * @return a working instance of an HttpClient - */ - private HttpClient getHttpClient() { - HttpClient ahc; - try { - final Class ahcClass = Class.forName("android.net.http.AndroidHttpClient"); - final Method newInstance = ahcClass.getMethod("newInstance", String.class); - ahc = (HttpClient) newInstance.invoke(null, "ImageCache"); - - } catch (final ClassNotFoundException e) { - DefaultHttpClient dhc = new DefaultHttpClient(); - final HttpParams params = dhc.getParams(); - dhc = null; - - params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 20 * 1000); - - final SchemeRegistry registry = new SchemeRegistry(); - registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); - - final ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params, - registry); - ahc = new DefaultHttpClient(manager, params); - - } catch (final NoSuchMethodException e) { - - final RuntimeException re = new RuntimeException("Programming error"); - re.initCause(e); - throw re; - - } catch (final IllegalAccessException e) { - final RuntimeException re = new RuntimeException("Programming error"); - re.initCause(e); - throw re; - - } catch (final InvocationTargetException e) { - final RuntimeException re = new RuntimeException("Programming error"); - re.initCause(e); - throw re; - } - return ahc; - } - - /** - *

- * Registers an {@link OnImageLoadListener} with the cache. When an image is loaded - * asynchronously either directly by way of {@link #scheduleLoadImage(int, Uri, int, int)} or - * indirectly by {@link #loadImage(int, Uri, int, int)}, any registered listeners will get - * called. - *

- * - *

- * This should probably be called from {@link Activity#onResume()}. - *

- */ - public void registerOnImageLoadListener(OnImageLoadListener onImageLoadListener) { - mImageLoadListeners.add(onImageLoadListener); - } - - /** - *

- * Unregisters the listener with the cache. This will not cancel any pending load requests. - *

- * - *

- * This should probably be called from {@link Activity#onPause()}. - *

- */ - public void unregisterOnImageLoadListener(OnImageLoadListener onImageLoadListener) { - mImageLoadListeners.remove(onImageLoadListener); - } - - private class LoadResult { - public LoadResult(int id, Uri image, Drawable drawable) { - this.id = id; - this.drawable = drawable; - this.image = image; - } - - final Uri image; - final int id; - final Drawable drawable; - } - - /** - * @param uri - * the image uri - * @return a key unique to the given uri - */ - public String getKey(Uri uri) { - return uri.toString(); - } - - /** - * Gets the given key as a drawable, retrieving it from memory cache if it's present. - * - * @param key - * a key generated by {@link #getKey(Uri)} or {@link #getKey(Uri, int, int)} - * @return the drawable if it's in the memory cache or null. - */ - public Drawable getDrawable(String key) { - final Drawable img = mMemCache.get(key); - if (img != null) { - if (DEBUG) { - Log.d(TAG, "mem cache hit for key " + key); - } - touchKey(key); - return img; - } - - return null; - } - - /** - * Puts a drawable into memory cache. - * - * @param key - * a key generated by {@link #getKey(Uri)} or {@link #getKey(Uri, int, int)} - */ - public void putDrawable(String key, Drawable drawable) { - mMemCache.put(key, drawable); - } - - /** - * A blocking call to get an image. If it's in the cache, it'll return the drawable immediately. - * Otherwise it will download, scale, and cache the image before returning it. For non-blocking - * use, see {@link #loadImage(int, Uri, int, int)} - * - * @throws ClientProtocolException - * @throws IOException - * @throws ImageCacheException - */ - public Drawable getImage(Uri uri, int width, int height) throws ClientProtocolException, - IOException, ImageCacheException { - - final String scaledKey = getKey(uri, width, height); - - mDownloading.lock(scaledKey); - - try { - Drawable d = getDrawable(scaledKey); - if (d != null) { - return d; - } - - Bitmap bmp = get(scaledKey); - - if (bmp == null) { - if ("file".equals(uri.getScheme())) { - bmp = scaleLocalImage(new File(uri.getPath()), width, height); - } else { - final String sourceKey = getKey(uri); - - mDownloading.lock(sourceKey); - - try { - if (!contains(sourceKey)) { - downloadImage(sourceKey, uri); - } - } finally { - mDownloading.unlock(sourceKey); - } - - bmp = scaleLocalImage(getFile(sourceKey), width, height); - if (bmp == null) { - clear(sourceKey); - } - } - put(scaledKey, bmp); - - } - if (bmp == null) { - throw new ImageCacheException("got null bitmap from request to scale"); - - } - d = new BitmapDrawable(mRes, bmp); - putDrawable(scaledKey, d); - - return d; - - } finally { - mDownloading.unlock(scaledKey); - } - } - - private final SparseArray mKeyCache = new SparseArray(); - - /** - * Returns an opaque cache key representing the given uri, width and height. - * - * @param uri - * an image uri - * @param width - * the desired image max width - * @param height - * the desired image max height - * @return a cache key unique to the given parameters - */ - public String getKey(Uri uri, int width, int height) { - // collisions are possible, but unlikely. - final int hashId = uri.hashCode() + width + height * 10000; - - String key = mKeyCache.get(hashId); - if (key == null) { - key = uri.buildUpon().appendQueryParameter("width", String.valueOf(width)) - .appendQueryParameter("height", String.valueOf(height)).build().toString(); - mKeyCache.put(hashId, key); - } - return key; - } - - @Override - public synchronized boolean clear() { - final boolean success = super.clear(); - - mMemCache.evictAll(); - - mKeyCache.clear(); - - return success; - } - - @Override - public synchronized boolean clear(String key) { - final boolean success = super.clear(key); - - mMemCache.remove(key); - - return success; - } - - private class ImageLoadTask implements Runnable, Comparable { - private final int id; - private final Uri uri; - private final int width; - private final int height; - private final long when = System.nanoTime(); - - public ImageLoadTask(int id, Uri image, int width, int height) { - this.id = id; - this.uri = image; - this.width = width; - this.height = height; - } - - @Override - public void run() { - - if (DEBUG) { - Log.d(TAG, "ImageLoadTask.doInBackground(" + id + ", " + uri + ", " + width + ", " - + height + ")"); - } - - try { - final LoadResult result = new LoadResult(id, uri, getImage(uri, width, height)); - synchronized (jobs) { - if (jobs.containsKey(id)) { - // Job still valid. - jobs.remove(id); - mHandler.obtainMessage(MSG_IMAGE_LOADED, result).sendToTarget(); - } - } - - // TODO this exception came about, no idea why: - // java.lang.IllegalArgumentException: Parser may not be null - } catch (final IllegalArgumentException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - } catch (final OutOfMemoryError oom) { - oomClear(); - } catch (final ClientProtocolException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - } catch (final IOException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - } catch (final ImageCacheException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - } - } - - @Override - public int compareTo(ImageLoadTask another) { - return Long.valueOf(another.when).compareTo(when); - }; - } - - private void oomClear() { - Log.w(TAG, "out of memory, clearing mem cache"); - mMemCache.evictAll(); - } - - /** - * Checks the cache for an image matching the given criteria and returns it. If it isn't - * immediately available, calls {@link #scheduleLoadImage}. - * - * @param id - * An ID to keep track of image load requests. For one-off loads, this can just be - * the ID of the {@link ImageView}. Otherwise, an unique ID can be acquired using - * {@link #getNewID()}. - * - * @param image - * the image to be loaded. Can be a local file or a network resource. - * @param width - * the maximum width of the resulting image - * @param height - * the maximum height of the resulting image - * @return the cached bitmap if it's available immediately or null if it needs to be loaded - * asynchronously. - */ - public Drawable loadImage(int id, Uri image, int width, int height) throws IOException { - if (DEBUG) { - Log.d(TAG, "loadImage(" + id + ", " + image + ", " + width + ", " + height + ")"); - } - final Drawable res = getDrawable(getKey(image, width, height)); - if (res == null) { - if (DEBUG) { - Log.d(TAG, - "Image not found in memory cache. Scheduling load from network / disk..."); - } - scheduleLoadImage(id, image, width, height); - } - return res; - } - - /** - * Deprecated to make IDs ints instead of longs. See {@link #loadImage(int, Uri, int, int)}. - * - * @throws IOException - */ - @Deprecated - public Drawable loadImage(long id, Uri image, int width, int height) throws IOException { - return loadImage(id, image, width, height); - } - - /** - * Schedules a load of the given image. When the image has finished loading and scaling, all - * registered {@link OnImageLoadListener}s will be called. - * - * @param id - * An ID to keep track of image load requests. For one-off loads, this can just be - * the ID of the {@link ImageView}. Otherwise, an unique ID can be acquired using - * {@link #getNewID()}. - * - * @param image - * the image to be loaded. Can be a local file or a network resource. - * @param width - * the maximum width of the resulting image - * @param height - * the maximum height of the resulting image - */ - public void scheduleLoadImage(int id, Uri image, int width, int height) { - if (DEBUG) { - Log.d(TAG, "executing new ImageLoadTask in background..."); - } - final ImageLoadTask imt = new ImageLoadTask(id, image, width, height); - - jobs.put(id, imt); - mExecutor.execute(imt); - } - - /** - * Deprecated in favour of {@link #scheduleLoadImage(int, Uri, int, int)}. - */ - @Deprecated - public void scheduleLoadImage(long id, Uri image, int width, int height) { - scheduleLoadImage(id, image, width, height); - } - - /** - * Cancels all the asynchronous image loads. Note: currently does not function properly. - * - */ - public void cancelLoads() { - jobs.clear(); - mExecutor.getQueue().clear(); - } - - public void cancel(int id) { - synchronized (jobs) { - final Runnable job = jobs.get(id); - if (job != null) { - jobs.remove(id); - mExecutor.remove(job); - if (DEBUG) { - Log.d(TAG, "removed load id " + id); - } - } - } - } - - /** - * Deprecated in favour of {@link #cancel(int)}. - */ - @Deprecated - public void cancel(long id) { - cancel(id); - } - - /** - * Blocking call to scale a local file. Scales using preserving aspect ratio - * - * @param localFile - * local image file to be scaled - * @param width - * maximum width - * @param height - * maximum height - * @return the scaled image - * @throws ClientProtocolException - * @throws IOException - */ - private static Bitmap scaleLocalImage(File localFile, int width, int height) - throws ClientProtocolException, IOException { - - if (DEBUG) { - Log.d(TAG, "scaleLocalImage(" + localFile + ", " + width + ", " + height + ")"); - } - - if (!localFile.exists()) { - throw new IOException("local file does not exist: " + localFile); - } - if (!localFile.canRead()) { - throw new IOException("cannot read from local file: " + localFile); - } - - // the below borrowed from: - // https://github.com/thest1/LazyList/blob/master/src/com/fedorvlasov/lazylist/ImageLoader.java - - // decode image size - final BitmapFactory.Options o = new BitmapFactory.Options(); - o.inJustDecodeBounds = true; - - BitmapFactory.decodeStream(new FileInputStream(localFile), null, o); - - // Find the correct scale value. It should be the power of 2. - //final int REQUIRED_WIDTH = width, REQUIRED_HEIGHT = height; - int width_tmp = o.outWidth, height_tmp = o.outHeight; - int scale = 1; - while (true) { - if (width_tmp / 2 <= width || height_tmp / 2 <= height) { - break; - } - width_tmp /= 2; - height_tmp /= 2; - scale *= 2; - } - - // decode with inSampleSize - final BitmapFactory.Options o2 = new BitmapFactory.Options(); - o2.inSampleSize = scale; - final Bitmap prescale = BitmapFactory - .decodeStream(new FileInputStream(localFile), null, o2); - - if (prescale == null) { - Log.e(TAG, localFile + " could not be decoded"); - } else if (DEBUG) { - Log.d(TAG, "Successfully completed scaling of " + localFile + " to " + width + "x" - + height); - } - - return prescale; - } - - /** - * Blocking call to download an image. The image is placed directly into the disk cache at the - * given key. - * - * @param uri - * the location of the image - * @throws ClientProtocolException - * if the HTTP response code wasn't 200 or any other HTTP errors - * @throws IOException - */ - protected void downloadImage(String key, Uri uri) throws ClientProtocolException, IOException { - if (DEBUG) { - Log.d(TAG, "downloadImage(" + key + ", " + uri + ")"); - } - if (USE_APACHE_NC) { - final HttpGet get = new HttpGet(uri.toString()); - final HttpParams params = get.getParams(); - params.setParameter(ClientPNames.HANDLE_REDIRECTS, true); - - final HttpResponse hr = hc.execute(get); - final StatusLine hs = hr.getStatusLine(); - if (hs.getStatusCode() != 200) { - throw new HttpResponseException(hs.getStatusCode(), hs.getReasonPhrase()); - } - - final HttpEntity ent = hr.getEntity(); - - // TODO I think this means that the source file must be a jpeg. fix this. - try { - - putRaw(key, ent.getContent()); - if (DEBUG) { - Log.d(TAG, "source file of " + uri + " saved to disk cache at location " - + getFile(key).getAbsolutePath()); - } - } finally { - ent.consumeContent(); - } - } else { - final URLConnection con = new URL(uri.toString()).openConnection(); - putRaw(key, con.getInputStream()); - if (DEBUG) { - Log.d(TAG, - "source file of " + uri + " saved to disk cache at location " - + getFile(key).getAbsolutePath()); - } - } - - } - - private void notifyListeners(LoadResult result) { - for (final OnImageLoadListener listener : mImageLoadListeners) { - listener.onImageLoaded(result.id, result.image, result.drawable); - } - } - - /** - * Implement this and register it using - * {@link ImageCache#registerOnImageLoadListener(OnImageLoadListener)} to be notified when - * asynchronous image loads have completed. - * - * @author Steve Pomeroy - * - */ - public interface OnImageLoadListener { - /** - * Called when the image has been loaded and scaled. - * - * @param id - * the ID provided by {@link ImageCache#loadImage(int, Uri, int, int)} or - * {@link ImageCache#scheduleLoadImage(int, Uri, int, int)} - * @param imageUri - * the uri of the image that was originally requested - * @param image - * the loaded and scaled image - */ - public void onImageLoaded(int id, Uri imageUri, Drawable image); - } -} diff --git a/astrid/src/main/java/edu/mit/mobile/android/imagecache/ImageCacheException.java b/astrid/src/main/java/edu/mit/mobile/android/imagecache/ImageCacheException.java deleted file mode 100644 index e2c9f0903..000000000 --- a/astrid/src/main/java/edu/mit/mobile/android/imagecache/ImageCacheException.java +++ /dev/null @@ -1,30 +0,0 @@ -package edu.mit.mobile.android.imagecache; - -/* - * Copyright (C) 2011-2012 MIT Mobile Experience Lab - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -public class ImageCacheException extends Exception { - - /** - * - */ - private static final long serialVersionUID = 997874306474290980L; - - public ImageCacheException(String message) { - super(message); - } -} diff --git a/astrid/src/main/java/edu/mit/mobile/android/imagecache/KeyedLock.java b/astrid/src/main/java/edu/mit/mobile/android/imagecache/KeyedLock.java deleted file mode 100644 index a323bb647..000000000 --- a/astrid/src/main/java/edu/mit/mobile/android/imagecache/KeyedLock.java +++ /dev/null @@ -1,72 +0,0 @@ -package edu.mit.mobile.android.imagecache; - -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A synchronization lock that creates a separate lock for each key. - * - * @author Steve Pomeroy - * - * @param - */ -public class KeyedLock { - private static final String TAG = KeyedLock.class.getSimpleName(); - - private final Map mLocks = new HashMap(); - - private static boolean DEBUG = false; - - public void lock(K key) { - if (DEBUG) { - log("acquiring lock for key " + key); - } - - ReentrantLock lock; - synchronized (mLocks) { - lock = mLocks.get(key); - if (lock == null) { - lock = new ReentrantLock(); - mLocks.put(key, lock); - if (DEBUG) { - log(lock + " created new lock and added it to map"); - } - - } - } - - lock.lock(); - } - - public void unlock(K key) { - if (DEBUG) { - log("unlocking lock for key " + key); - } - ReentrantLock lock; - - synchronized (mLocks) { - lock = mLocks.get(key); - if (lock == null) { - Log.e(TAG, "Attempting to unlock lock for key " + key + " which has no entry"); - return; - } - if (DEBUG) { - log(lock + " has queued threads " + lock.hasQueuedThreads() + " for key " + key); - } - // maybe entries should be removed when there are no queued threads. This would - // occasionally fail... - // final boolean queued = lock.hasQueuedThreads(); - - lock.unlock(); - } - } - - private void log(String message) { - - Log.d(TAG, Thread.currentThread().getId() + "\t" + message); - - } -} diff --git a/astrid/src/main/res/layout/update_adapter_row.xml b/astrid/src/main/res/layout/update_adapter_row.xml index 382cc81c1..26e0656c6 100644 --- a/astrid/src/main/res/layout/update_adapter_row.xml +++ b/astrid/src/main/res/layout/update_adapter_row.xml @@ -49,12 +49,12 @@ -