(maxSize);
215 | }
216 |
217 | private static String getExtension(CompressFormat format) {
218 | String extension;
219 | switch (format) {
220 | case JPEG:
221 | extension = ".jpg";
222 | break;
223 |
224 | case PNG:
225 | extension = ".png";
226 | break;
227 |
228 | default:
229 | throw new IllegalArgumentException();
230 | }
231 |
232 | return extension;
233 | }
234 |
235 | /**
236 | * If loading a number of images where you don't have a unique ID to represent the individual
237 | * load, this can be used to generate a sequential ID.
238 | *
239 | * @return a new unique ID
240 | */
241 | public int getNewID() {
242 | synchronized (mIDCounter) {
243 | return mIDCounter++;
244 | }
245 | }
246 |
247 | @Override
248 | protected Bitmap fromDisk(String key, InputStream in) {
249 |
250 | if (DEBUG) {
251 | Log.d(TAG, "disk cache hit for key " + key);
252 | }
253 | try {
254 | final Bitmap image = BitmapFactory.decodeStream(in);
255 | return image;
256 |
257 | } catch (final OutOfMemoryError oom) {
258 | oomClear();
259 | return null;
260 | }
261 | }
262 |
263 | @Override
264 | protected void toDisk(String key, Bitmap image, OutputStream out) {
265 | if (DEBUG) {
266 | Log.d(TAG, "disk cache write for key " + key);
267 | }
268 | if (image != null) {
269 | if (!image.compress(mCompressFormat, mQuality, out)) {
270 | Log.e(TAG, "error writing compressed image to disk for key " + key);
271 | }
272 | } else {
273 | Log.e(TAG, "Ignoring attempt to write null image to disk cache");
274 | }
275 | }
276 |
277 | /**
278 | * Gets an instance of AndroidHttpClient if the devices has it (it was introduced in 2.2), or
279 | * falls back on a http client that should work reasonably well.
280 | *
281 | * @return a working instance of an HttpClient
282 | */
283 | private HttpClient getHttpClient() {
284 | HttpClient ahc;
285 | try {
286 | final Class> ahcClass = Class.forName("android.net.http.AndroidHttpClient");
287 | final Method newInstance = ahcClass.getMethod("newInstance", String.class);
288 | ahc = (HttpClient) newInstance.invoke(null, "ImageCache");
289 |
290 | } catch (final ClassNotFoundException e) {
291 | DefaultHttpClient dhc = new DefaultHttpClient();
292 | final HttpParams params = dhc.getParams();
293 | dhc = null;
294 |
295 | params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 20 * 1000);
296 |
297 | final SchemeRegistry registry = new SchemeRegistry();
298 | registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
299 | registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
300 |
301 | final ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params,
302 | registry);
303 | ahc = new DefaultHttpClient(manager, params);
304 |
305 | } catch (final NoSuchMethodException e) {
306 |
307 | final RuntimeException re = new RuntimeException("Programming error");
308 | re.initCause(e);
309 | throw re;
310 |
311 | } catch (final IllegalAccessException e) {
312 | final RuntimeException re = new RuntimeException("Programming error");
313 | re.initCause(e);
314 | throw re;
315 |
316 | } catch (final InvocationTargetException e) {
317 | final RuntimeException re = new RuntimeException("Programming error");
318 | re.initCause(e);
319 | throw re;
320 | }
321 | return ahc;
322 | }
323 |
324 | /**
325 | *
326 | * Registers an {@link OnImageLoadListener} with the cache. When an image is loaded
327 | * asynchronously either directly by way of {@link #scheduleLoadImage(int, Uri, int, int)} or
328 | * indirectly by {@link #loadImage(int, Uri, int, int)}, any registered listeners will get
329 | * called.
330 | *
331 | *
332 | *
333 | * This should probably be called from {@link Activity#onResume()}.
334 | *
335 | *
336 | * @param onImageLoadListener
337 | */
338 | public void registerOnImageLoadListener(OnImageLoadListener onImageLoadListener) {
339 | mImageLoadListeners.add(onImageLoadListener);
340 | }
341 |
342 | /**
343 | *
344 | * Unregisters the listener with the cache. This will not cancel any pending load requests.
345 | *
346 | *
347 | *
348 | * This should probably be called from {@link Activity#onPause()}.
349 | *
350 | *
351 | * @param onImageLoadListener
352 | */
353 | public void unregisterOnImageLoadListener(OnImageLoadListener onImageLoadListener) {
354 | mImageLoadListeners.remove(onImageLoadListener);
355 | }
356 |
357 | private class LoadResult {
358 | public LoadResult(int id, Uri image, Drawable drawable) {
359 | this.id = id;
360 | this.drawable = drawable;
361 | this.image = image;
362 | }
363 |
364 | final Uri image;
365 | final int id;
366 | final Drawable drawable;
367 | }
368 |
369 | /**
370 | * @param uri
371 | * the image uri
372 | * @return a key unique to the given uri
373 | */
374 | public String getKey(Uri uri) {
375 | return uri.toString();
376 | }
377 |
378 | /**
379 | * Gets the given key as a drawable, retrieving it from memory cache if it's present.
380 | *
381 | * @param key
382 | * a key generated by {@link #getKey(Uri)} or {@link #getKey(Uri, int, int)}
383 | * @return the drawable if it's in the memory cache or null.
384 | */
385 | public Drawable getDrawable(String key) {
386 | final Drawable img = mMemCache.get(key);
387 | if (img != null) {
388 | if (DEBUG) {
389 | Log.d(TAG, "mem cache hit for key " + key);
390 | }
391 | touchKey(key);
392 | return img;
393 | }
394 |
395 | return null;
396 | }
397 |
398 | /**
399 | * Puts a drawable into memory cache.
400 | *
401 | * @param key
402 | * a key generated by {@link #getKey(Uri)} or {@link #getKey(Uri, int, int)}
403 | * @param drawable
404 | */
405 | public void putDrawable(String key, Drawable drawable) {
406 | mMemCache.put(key, drawable);
407 | }
408 |
409 | /**
410 | * A blocking call to get an image. If it's in the cache, it'll return the drawable immediately.
411 | * Otherwise it will download, scale, and cache the image before returning it. For non-blocking
412 | * use, see {@link #loadImage(int, Uri, int, int)}
413 | *
414 | * @param uri
415 | * @param width
416 | * @param height
417 | * @return
418 | * @throws ClientProtocolException
419 | * @throws IOException
420 | * @throws ImageCacheException
421 | */
422 | public Drawable getImage(Uri uri, int width, int height) throws ClientProtocolException,
423 | IOException, ImageCacheException {
424 |
425 | final String scaledKey = getKey(uri, width, height);
426 |
427 | mDownloading.lock(scaledKey);
428 |
429 | try {
430 | Drawable d = getDrawable(scaledKey);
431 | if (d != null) {
432 | return d;
433 | }
434 |
435 | Bitmap bmp = get(scaledKey);
436 |
437 | if (bmp == null) {
438 | if ("file".equals(uri.getScheme())) {
439 | bmp = scaleLocalImage(new File(uri.getPath()), width, height);
440 | } else {
441 | final String sourceKey = getKey(uri);
442 |
443 | mDownloading.lock(sourceKey);
444 |
445 | try {
446 | if (!contains(sourceKey)) {
447 | downloadImage(sourceKey, uri);
448 | }
449 | } finally {
450 | mDownloading.unlock(sourceKey);
451 | }
452 |
453 | bmp = scaleLocalImage(getFile(sourceKey), width, height);
454 | if (bmp == null) {
455 | clear(sourceKey);
456 | }
457 | }
458 | put(scaledKey, bmp);
459 |
460 | }
461 | if (bmp == null) {
462 | throw new ImageCacheException("got null bitmap from request to scale");
463 |
464 | }
465 | d = new BitmapDrawable(mRes, bmp);
466 | putDrawable(scaledKey, d);
467 |
468 | return d;
469 |
470 | } finally {
471 | mDownloading.unlock(scaledKey);
472 | }
473 | }
474 |
475 | private final SparseArray mKeyCache = new SparseArray();
476 |
477 | /**
478 | * Returns an opaque cache key representing the given uri, width and height.
479 | *
480 | * @param uri
481 | * an image uri
482 | * @param width
483 | * the desired image max width
484 | * @param height
485 | * the desired image max height
486 | * @return a cache key unique to the given parameters
487 | */
488 | public String getKey(Uri uri, int width, int height) {
489 | // collisions are possible, but unlikely.
490 | final int hashId = uri.hashCode() + width + height * 10000;
491 |
492 | String key = mKeyCache.get(hashId);
493 | if (key == null) {
494 | key = uri.buildUpon().appendQueryParameter("width", String.valueOf(width))
495 | .appendQueryParameter("height", String.valueOf(height)).build().toString();
496 | mKeyCache.put(hashId, key);
497 | }
498 | return key;
499 | }
500 |
501 | @Override
502 | public synchronized boolean clear() {
503 | final boolean success = super.clear();
504 |
505 | mMemCache.evictAll();
506 |
507 | mKeyCache.clear();
508 |
509 | return success;
510 | }
511 |
512 | @Override
513 | public synchronized boolean clear(String key) {
514 | final boolean success = super.clear(key);
515 |
516 | mMemCache.remove(key);
517 |
518 | return success;
519 | }
520 |
521 | private class ImageLoadTask implements Runnable, Comparable {
522 | private final int id;
523 | private final Uri uri;
524 | private final int width;
525 | private final int height;
526 | private final long when = System.nanoTime();
527 |
528 | public ImageLoadTask(int id, Uri image, int width, int height) {
529 | this.id = id;
530 | this.uri = image;
531 | this.width = width;
532 | this.height = height;
533 | }
534 |
535 | @Override
536 | public void run() {
537 |
538 | if (DEBUG) {
539 | Log.d(TAG, "ImageLoadTask.doInBackground(" + id + ", " + uri + ", " + width + ", "
540 | + height + ")");
541 | }
542 |
543 | try {
544 | final LoadResult result = new LoadResult(id, uri, getImage(uri, width, height));
545 | synchronized (jobs) {
546 | if (jobs.containsKey(id)) {
547 | // Job still valid.
548 | jobs.remove(id);
549 | mHandler.obtainMessage(MSG_IMAGE_LOADED, result).sendToTarget();
550 | }
551 | }
552 |
553 | // TODO this exception came about, no idea why:
554 | // java.lang.IllegalArgumentException: Parser may not be null
555 | } catch (final IllegalArgumentException e) {
556 | Log.e(TAG, e.getLocalizedMessage(), e);
557 | } catch (final OutOfMemoryError oom) {
558 | oomClear();
559 | } catch (final ClientProtocolException e) {
560 | Log.e(TAG, e.getLocalizedMessage(), e);
561 | } catch (final IOException e) {
562 | Log.e(TAG, e.getLocalizedMessage(), e);
563 | } catch (final ImageCacheException e) {
564 | Log.e(TAG, e.getLocalizedMessage(), e);
565 | }
566 | }
567 |
568 | @Override
569 | public int compareTo(ImageLoadTask another) {
570 | return Long.valueOf(another.when).compareTo(when);
571 | };
572 | }
573 |
574 | private void oomClear() {
575 | Log.w(TAG, "out of memory, clearing mem cache");
576 | mMemCache.evictAll();
577 | }
578 |
579 | /**
580 | * Checks the cache for an image matching the given criteria and returns it. If it isn't
581 | * immediately available, calls {@link #scheduleLoadImage}.
582 | *
583 | * @param id
584 | * An ID to keep track of image load requests. For one-off loads, this can just be
585 | * the ID of the {@link ImageView}. Otherwise, an unique ID can be acquired using
586 | * {@link #getNewID()}.
587 | *
588 | * @param image
589 | * the image to be loaded. Can be a local file or a network resource.
590 | * @param width
591 | * the maximum width of the resulting image
592 | * @param height
593 | * the maximum height of the resulting image
594 | * @return the cached bitmap if it's available immediately or null if it needs to be loaded
595 | * asynchronously.
596 | */
597 | public Drawable loadImage(int id, Uri image, int width, int height) throws IOException {
598 | if (DEBUG) {
599 | Log.d(TAG, "loadImage(" + id + ", " + image + ", " + width + ", " + height + ")");
600 | }
601 | final Drawable res = getDrawable(getKey(image, width, height));
602 | if (res == null) {
603 | if (DEBUG) {
604 | Log.d(TAG,
605 | "Image not found in memory cache. Scheduling load from network / disk...");
606 | }
607 | scheduleLoadImage(id, image, width, height);
608 | }
609 | return res;
610 | }
611 |
612 | /**
613 | * Deprecated to make IDs ints instead of longs. See {@link #loadImage(int, Uri, int, int)}.
614 | *
615 | * @param id
616 | * @param image
617 | * @param width
618 | * @param height
619 | * @return
620 | * @throws IOException
621 | */
622 | @Deprecated
623 | public Drawable loadImage(long id, Uri image, int width, int height) throws IOException {
624 | return loadImage(id, image, width, height);
625 | }
626 |
627 | /**
628 | * Schedules a load of the given image. When the image has finished loading and scaling, all
629 | * registered {@link OnImageLoadListener}s will be called.
630 | *
631 | * @param id
632 | * An ID to keep track of image load requests. For one-off loads, this can just be
633 | * the ID of the {@link ImageView}. Otherwise, an unique ID can be acquired using
634 | * {@link #getNewID()}.
635 | *
636 | * @param image
637 | * the image to be loaded. Can be a local file or a network resource.
638 | * @param width
639 | * the maximum width of the resulting image
640 | * @param height
641 | * the maximum height of the resulting image
642 | */
643 | public void scheduleLoadImage(int id, Uri image, int width, int height) {
644 | if (DEBUG) {
645 | Log.d(TAG, "executing new ImageLoadTask in background...");
646 | }
647 | final ImageLoadTask imt = new ImageLoadTask(id, image, width, height);
648 |
649 | jobs.put(id, imt);
650 | mExecutor.execute(imt);
651 | }
652 |
653 | /**
654 | * Deprecated in favour of {@link #scheduleLoadImage(int, Uri, int, int)}.
655 | *
656 | * @param id
657 | * @param image
658 | * @param width
659 | * @param height
660 | */
661 | @Deprecated
662 | public void scheduleLoadImage(long id, Uri image, int width, int height) {
663 | scheduleLoadImage(id, image, width, height);
664 | }
665 |
666 | /**
667 | * Cancels all the asynchronous image loads. Note: currently does not function properly.
668 | *
669 | */
670 | public void cancelLoads() {
671 | jobs.clear();
672 | mExecutor.getQueue().clear();
673 | }
674 |
675 | public void cancel(int id) {
676 | synchronized (jobs) {
677 | final Runnable job = jobs.get(id);
678 | if (job != null) {
679 | jobs.remove(id);
680 | mExecutor.remove(job);
681 | if (DEBUG) {
682 | Log.d(TAG, "removed load id " + id);
683 | }
684 | }
685 | }
686 | }
687 |
688 | /**
689 | * Deprecated in favour of {@link #cancel(int)}.
690 | *
691 | * @param id
692 | */
693 | @Deprecated
694 | public void cancel(long id) {
695 | cancel(id);
696 | }
697 |
698 | /**
699 | * Blocking call to scale a local file. Scales using preserving aspect ratio
700 | *
701 | * @param localFile
702 | * local image file to be scaled
703 | * @param width
704 | * maximum width
705 | * @param height
706 | * maximum height
707 | * @return the scaled image
708 | * @throws ClientProtocolException
709 | * @throws IOException
710 | */
711 | private static Bitmap scaleLocalImage(File localFile, int width, int height)
712 | throws ClientProtocolException, IOException {
713 |
714 | if (DEBUG) {
715 | Log.d(TAG, "scaleLocalImage(" + localFile + ", " + width + ", " + height + ")");
716 | }
717 |
718 | if (!localFile.exists()) {
719 | throw new IOException("local file does not exist: " + localFile);
720 | }
721 | if (!localFile.canRead()) {
722 | throw new IOException("cannot read from local file: " + localFile);
723 | }
724 |
725 | // the below borrowed from:
726 | // https://github.com/thest1/LazyList/blob/master/src/com/fedorvlasov/lazylist/ImageLoader.java
727 |
728 | // decode image size
729 | final BitmapFactory.Options o = new BitmapFactory.Options();
730 | o.inJustDecodeBounds = true;
731 |
732 | BitmapFactory.decodeStream(new FileInputStream(localFile), null, o);
733 |
734 | // Find the correct scale value. It should be the power of 2.
735 | //final int REQUIRED_WIDTH = width, REQUIRED_HEIGHT = height;
736 | int width_tmp = o.outWidth, height_tmp = o.outHeight;
737 | int scale = 1;
738 | while (true) {
739 | if (width_tmp / 2 <= width || height_tmp / 2 <= height) {
740 | break;
741 | }
742 | width_tmp /= 2;
743 | height_tmp /= 2;
744 | scale *= 2;
745 | }
746 |
747 | // decode with inSampleSize
748 | final BitmapFactory.Options o2 = new BitmapFactory.Options();
749 | o2.inSampleSize = scale;
750 | final Bitmap prescale = BitmapFactory
751 | .decodeStream(new FileInputStream(localFile), null, o2);
752 |
753 | if (prescale == null) {
754 | Log.e(TAG, localFile + " could not be decoded");
755 | } else if (DEBUG) {
756 | Log.d(TAG, "Successfully completed scaling of " + localFile + " to " + width + "x"
757 | + height);
758 | }
759 |
760 | return prescale;
761 | }
762 |
763 | /**
764 | * Blocking call to download an image. The image is placed directly into the disk cache at the
765 | * given key.
766 | *
767 | * @param uri
768 | * the location of the image
769 | * @return a decoded bitmap
770 | * @throws ClientProtocolException
771 | * if the HTTP response code wasn't 200 or any other HTTP errors
772 | * @throws IOException
773 | */
774 | protected void downloadImage(String key, Uri uri) throws ClientProtocolException, IOException {
775 | if (DEBUG) {
776 | Log.d(TAG, "downloadImage(" + key + ", " + uri + ")");
777 | }
778 | if (USE_APACHE_NC) {
779 | final HttpGet get = new HttpGet(uri.toString());
780 | final HttpParams params = get.getParams();
781 | params.setParameter(ClientPNames.HANDLE_REDIRECTS, true);
782 |
783 | final HttpResponse hr = hc.execute(get);
784 | final StatusLine hs = hr.getStatusLine();
785 | if (hs.getStatusCode() != 200) {
786 | throw new HttpResponseException(hs.getStatusCode(), hs.getReasonPhrase());
787 | }
788 |
789 | final HttpEntity ent = hr.getEntity();
790 |
791 | // TODO I think this means that the source file must be a jpeg. fix this.
792 | try {
793 |
794 | putRaw(key, ent.getContent());
795 | if (DEBUG) {
796 | Log.d(TAG, "source file of " + uri + " saved to disk cache at location "
797 | + getFile(key).getAbsolutePath());
798 | }
799 | } finally {
800 | ent.consumeContent();
801 | }
802 | } else {
803 | final URLConnection con = new URL(uri.toString()).openConnection();
804 | putRaw(key, con.getInputStream());
805 | if (DEBUG) {
806 | Log.d(TAG,
807 | "source file of " + uri + " saved to disk cache at location "
808 | + getFile(key).getAbsolutePath());
809 | }
810 | }
811 |
812 | }
813 |
814 | private void notifyListeners(LoadResult result) {
815 | for (final OnImageLoadListener listener : mImageLoadListeners) {
816 | listener.onImageLoaded(result.id, result.image, result.drawable);
817 | }
818 | }
819 |
820 | /**
821 | * Implement this and register it using
822 | * {@link ImageCache#registerOnImageLoadListener(OnImageLoadListener)} to be notified when
823 | * asynchronous image loads have completed.
824 | *
825 | * @author Steve Pomeroy
826 | *
827 | */
828 | public interface OnImageLoadListener {
829 | /**
830 | * Called when the image has been loaded and scaled.
831 | *
832 | * @param id
833 | * the ID provided by {@link ImageCache#loadImage(int, Uri, int, int)} or
834 | * {@link ImageCache#scheduleLoadImage(int, Uri, int, int)}
835 | * @param imageUri
836 | * the uri of the image that was originally requested
837 | * @param image
838 | * the loaded and scaled image
839 | */
840 | public void onImageLoaded(int id, Uri imageUri, Drawable image);
841 | }
842 | }
843 |
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/imagecache/ImageCacheException.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache;
2 |
3 | /*
4 | * Copyright (C) 2011-2012 MIT Mobile Experience Lab
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public
17 | * License along with this library; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 | */
20 | public class ImageCacheException extends Exception {
21 |
22 | /**
23 | *
24 | */
25 | private static final long serialVersionUID = 997874306474290980L;
26 |
27 | public ImageCacheException(String message) {
28 | super(message);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/imagecache/ImageLoaderAdapter.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache;
2 |
3 | /*
4 | * Copyright (C) 2011-2013 MIT Mobile Experience Lab
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public
17 | * License along with this library; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 | */
20 | import java.io.IOException;
21 | import java.lang.ref.SoftReference;
22 |
23 | import android.app.Activity;
24 | import android.content.Context;
25 | import android.graphics.drawable.Drawable;
26 | import android.net.Uri;
27 | import android.util.Log;
28 | import android.util.SparseArray;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 | import android.widget.ImageView;
32 | import android.widget.ListAdapter;
33 |
34 | import com.commonsware.cwac.adapter.AdapterWrapper;
35 |
36 | /**
37 | *
38 | * An adapter that wraps another adapter, loading images into ImageViews asynchronously.
39 | *
40 | *
41 | *
42 | * To use, pass in a {@link ListAdapter} that generates {@link ImageView}s in the layout hierarchy
43 | * of getView(). ImageViews are searched for using the IDs specified in {@code imageViewIDs}. When
44 | * found, {@link ImageView#getTag(R.id.ic__uri)} is called and should return a {@link Uri}
45 | * referencing a local or remote image. See {@link ImageCache#loadImage(int, Uri, int, int)} for
46 | * details on the types of URIs and images supported.
47 | *
48 | *
49 | * @author Steve Pomeroy
50 | *
51 | */
52 | public class ImageLoaderAdapter extends AdapterWrapper implements ImageCache.OnImageLoadListener {
53 | private static final String TAG = ImageLoaderAdapter.class.getSimpleName();
54 |
55 | /**
56 | * The unit specified is in pixels
57 | */
58 | public static final int UNIT_PX = 0;
59 |
60 | /**
61 | * The unit specified is in density-independent pixels (DIP)
62 | */
63 | public static final int UNIT_DIP = 1;
64 |
65 | // //////////////////////////////////////////////
66 | // / private
67 | // //////////////////////////////////////////////
68 |
69 | private final SparseArray> mImageViewsToLoad = new SparseArray>();
70 |
71 | private final int[] mImageViewIDs;
72 | private final ImageCache mCache;
73 |
74 | private final int mDefaultWidth, mDefaultHeight;
75 |
76 | private final boolean mAutosize;
77 |
78 | private final SparseArray mViewDimensionCache;
79 |
80 | // ///////////////////////////////////////////////
81 |
82 | /**
83 | * Like the
84 | * {@link #ImageLoaderAdapter(Context, ListAdapter, ImageCache, int[], int, int, int, boolean)}
85 | * constructor with a default of {@code true} for autosize.
86 | *
87 | * @param context
88 | * a context for getting the display density. You don't need to worry about this
89 | * class holding on to a reference to this: it's only used in the constructor.
90 | * @param wrapped
91 | * the adapter that's wrapped. See {@link ImageLoaderAdapter} for the requirements of
92 | * using this adapter wrapper.
93 | * @param cache
94 | * an instance of your image cache. This can be shared with the process.
95 | * @param imageViewIDs
96 | * a list of resource IDs matching the ImageViews that should be scanned and loaded.
97 | * @param defaultWidth
98 | * the default maximum width, in the specified unit. This size will be used if the
99 | * size cannot be obtained from the view.
100 | * @param defaultHeight
101 | * the default maximum height, in the specified unit. This size will be used if the
102 | * size cannot be obtained from the view.
103 | * @param unit
104 | * one of {@link #UNIT_PX} or {@link #UNIT_DIP}
105 | */
106 | public ImageLoaderAdapter(Context context, ListAdapter wrapped, ImageCache cache,
107 | int[] imageViewIDs, int defaultWidth, int defaultHeight, int unit) {
108 | this(context, wrapped, cache, imageViewIDs, defaultWidth, defaultHeight, unit, true);
109 | }
110 |
111 | /**
112 | * @param context
113 | * a context for getting the display density. You don't need to worry about this
114 | * class holding on to a reference to this: it's only used in the constructor.
115 | * @param wrapped
116 | * the adapter that's wrapped. See {@link ImageLoaderAdapter} for the requirements of
117 | * using this adapter wrapper.
118 | * @param cache
119 | * an instance of your image cache. This can be shared with the process.
120 | * @param imageViewIDs
121 | * a list of resource IDs matching the ImageViews that should be scanned and loaded.
122 | * @param defaultWidth
123 | * the default maximum width, in the specified unit. This size will be used if the
124 | * size cannot be obtained from the view.
125 | * @param defaultHeight
126 | * the default maximum height, in the specified unit. This size will be used if the
127 | * size cannot be obtained from the view.
128 | * @param unit
129 | * one of {@link #UNIT_PX} or {@link #UNIT_DIP}
130 | * @param autosize
131 | * if true, the view's dimensions will be cached the first time it's loaded and an
132 | * image of the appropriate size will be requested the next time an image is loaded.
133 | * False uses defaultWidth and defaultHeight only.
134 | */
135 | public ImageLoaderAdapter(Context context, ListAdapter wrapped, ImageCache cache,
136 | int[] imageViewIDs, int defaultWidth, int defaultHeight, int unit, boolean autosize) {
137 | super(wrapped);
138 |
139 | mImageViewIDs = imageViewIDs;
140 | mCache = cache;
141 | mCache.registerOnImageLoadListener(this);
142 |
143 | mAutosize = autosize;
144 |
145 | if (autosize) {
146 | mViewDimensionCache = new SparseArray();
147 | } else {
148 | mViewDimensionCache = null;
149 | }
150 |
151 | switch (unit) {
152 | case UNIT_PX:
153 | mDefaultHeight = defaultHeight;
154 | mDefaultWidth = defaultWidth;
155 | break;
156 |
157 | case UNIT_DIP: {
158 | final float scale = context.getResources().getDisplayMetrics().density;
159 | mDefaultHeight = (int) (defaultHeight * scale);
160 | mDefaultWidth = (int) (defaultWidth * scale);
161 | }
162 | break;
163 |
164 | default:
165 | throw new IllegalArgumentException("invalid unit type");
166 |
167 | }
168 | }
169 |
170 | /**
171 | * Constructs a new adapter with a default unit of pixels.
172 | *
173 | * @param wrapped
174 | * the adapter that's wrapped. See {@link ImageLoaderAdapter} for the requirements of
175 | * using this adapter wrapper.
176 | * @param cache
177 | * an instance of your image cache. This can be shared with the process.
178 | * @param imageViewIDs
179 | * a list of resource IDs matching the ImageViews that should be scan
180 | * @param width
181 | * the maximum width, in pixels
182 | * @param height
183 | * the maximum height, in pixels
184 | */
185 | public ImageLoaderAdapter(ListAdapter wrapped, ImageCache cache, int[] imageViewIDs, int width,
186 | int height) {
187 | this(null, wrapped, cache, imageViewIDs, width, height, UNIT_PX);
188 | }
189 |
190 | @Override
191 | protected void finalize() throws Throwable {
192 | unregisterOnImageLoadListener();
193 | super.finalize();
194 | }
195 |
196 | /**
197 | * This can be called from your {@link Activity#onResume()} method.
198 | */
199 | public void registerOnImageLoadListener() {
200 | mCache.registerOnImageLoadListener(this);
201 | }
202 |
203 | /**
204 | * This can be called from your {@link Activity#onPause()} method.
205 | */
206 | public void unregisterOnImageLoadListener() {
207 | mCache.unregisterOnImageLoadListener(this);
208 | }
209 |
210 | @Override
211 | public View getView(int position, View convertView, ViewGroup parent) {
212 | final View v = super.getView(position, convertView, parent);
213 |
214 | for (final int id : mImageViewIDs) {
215 | if (convertView != null) {
216 | final ImageView iv = (ImageView) convertView.findViewById(id);
217 | if (iv != null) {
218 | final Integer tagId = (Integer) iv.getTag(R.id.ic__load_id);
219 | if (tagId != null) {
220 | mCache.cancel(tagId);
221 | }
222 | }
223 | }
224 |
225 | final ImageView iv = (ImageView) v.findViewById(id);
226 | if (iv == null) {
227 | continue;
228 | }
229 |
230 | final Uri tag = (Uri) iv.getTag(R.id.ic__uri);
231 | // short circuit if there's no tag
232 | if (tag == null) {
233 | continue;
234 | }
235 |
236 | ViewDimensionCache viewDimension = null;
237 |
238 | if (mAutosize) {
239 | viewDimension = mViewDimensionCache.get(id);
240 | if (viewDimension == null) {
241 | final int w = iv.getMeasuredWidth();
242 | final int h = iv.getMeasuredHeight();
243 | if (w > 0 && h > 0) {
244 | viewDimension = new ViewDimensionCache();
245 | viewDimension.width = w;
246 | viewDimension.height = h;
247 | mViewDimensionCache.put(id, viewDimension);
248 | }
249 | }
250 | }
251 |
252 | final int imageID = mCache.getNewID();
253 |
254 | // ic__load_id is used to keep track of what load ID is associated with what
255 | // particular ImageView
256 |
257 | iv.setTag(R.id.ic__load_id, imageID);
258 | // attempt to bypass all the loading machinery to get the image loaded as quickly
259 | // as possible
260 | Drawable d = null;
261 | try {
262 | if (viewDimension != null && viewDimension.width > 0 && viewDimension.height > 0) {
263 | d = mCache.loadImage(imageID, tag, viewDimension.width, viewDimension.height);
264 | } else {
265 | d = mCache.loadImage(imageID, tag, mDefaultWidth, mDefaultHeight);
266 | }
267 | } catch (final IOException e) {
268 | e.printStackTrace();
269 | }
270 | if (d != null) {
271 | iv.setImageDrawable(d);
272 | } else {
273 | if (ImageCache.DEBUG) {
274 | Log.d(TAG, "scheduling load with ID: " + imageID + "; URI;" + tag);
275 | }
276 | mImageViewsToLoad.put(imageID, new SoftReference(iv));
277 | }
278 |
279 | }
280 | return v;
281 | }
282 |
283 | @Override
284 | public void onImageLoaded(int id, Uri imageUri, Drawable image) {
285 | final SoftReference ivRef = mImageViewsToLoad.get(id);
286 | if (ivRef == null) {
287 | return;
288 | }
289 | final ImageView iv = ivRef.get();
290 | if (iv == null) {
291 | mImageViewsToLoad.remove(id);
292 | return;
293 | }
294 | if (ImageCache.DEBUG) {
295 | Log.d(TAG, "loading ID " + id + " with an image");
296 | }
297 | if (imageUri.equals(iv.getTag(R.id.ic__uri))) {
298 | iv.setImageDrawable(image);
299 | }
300 | mImageViewsToLoad.remove(id);
301 | }
302 |
303 | private static class ViewDimensionCache {
304 | int width;
305 | int height;
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/imagecache/KeyedLock.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.concurrent.locks.ReentrantLock;
6 |
7 | import android.util.Log;
8 |
9 | /**
10 | * A synchronization lock that creates a separate lock for each key.
11 | *
12 | * @author Steve Pomeroy
13 | *
14 | * @param
15 | */
16 | public class KeyedLock {
17 | private static final String TAG = KeyedLock.class.getSimpleName();
18 |
19 | private final Map mLocks = new HashMap();
20 |
21 | private static boolean DEBUG = false;
22 |
23 | /**
24 | * @param key
25 | */
26 | public void lock(K key) {
27 | if (DEBUG) {
28 | log("acquiring lock for key " + key);
29 | }
30 |
31 | ReentrantLock lock;
32 | synchronized (mLocks) {
33 | lock = mLocks.get(key);
34 | if (lock == null) {
35 | lock = new ReentrantLock();
36 | mLocks.put(key, lock);
37 | if (DEBUG) {
38 | log(lock + " created new lock and added it to map");
39 | }
40 |
41 | }
42 | }
43 |
44 | lock.lock();
45 | }
46 |
47 | /**
48 | * @param key
49 | */
50 | public void unlock(K key) {
51 | if (DEBUG) {
52 | log("unlocking lock for key " + key);
53 | }
54 | ReentrantLock lock;
55 |
56 | synchronized (mLocks) {
57 | lock = mLocks.get(key);
58 | if (lock == null) {
59 | Log.e(TAG, "Attempting to unlock lock for key " + key + " which has no entry");
60 | return;
61 | }
62 | if (DEBUG) {
63 | log(lock + " has queued threads " + lock.hasQueuedThreads() + " for key " + key);
64 | }
65 | // maybe entries should be removed when there are no queued threads. This would
66 | // occasionally fail...
67 | // final boolean queued = lock.hasQueuedThreads();
68 |
69 | lock.unlock();
70 | }
71 | }
72 |
73 | private void log(String message) {
74 |
75 | Log.d(TAG, Thread.currentThread().getId() + "\t" + message);
76 |
77 | }
78 | }
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/imagecache/SimpleThumbnailAdapter.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache;
2 |
3 | /*
4 | * Copyright (C) 2011-2012 MIT Mobile Experience Lab
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public
17 | * License along with this library; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 | */
20 | import java.util.List;
21 | import java.util.Map;
22 |
23 | import android.content.Context;
24 | import android.graphics.drawable.Drawable;
25 | import android.net.Uri;
26 | import android.view.LayoutInflater;
27 | import android.view.View;
28 | import android.widget.ImageView;
29 | import android.widget.SimpleAdapter;
30 |
31 | public class SimpleThumbnailAdapter extends SimpleAdapter {
32 | private final Drawable defaultImages[];
33 | private final int[] mImageIDs;
34 |
35 | public SimpleThumbnailAdapter(Context context,
36 | List extends Map> data, int layout, String[] from,
37 | int[] to, int[]imageIDs) {
38 | super(context, data, layout, from, to);
39 |
40 | final View v = LayoutInflater.from(context)
41 | .inflate(layout, null, false);
42 | defaultImages = new Drawable[imageIDs.length];
43 |
44 | mImageIDs = imageIDs;
45 |
46 | for (int i = 0; i < mImageIDs.length; i++) {
47 | final ImageView thumb = (ImageView) v.findViewById(imageIDs[i]);
48 | defaultImages[i] = thumb.getDrawable();
49 | }
50 |
51 | }
52 |
53 | @Override
54 | public void setViewImage(ImageView v, String value) {
55 | final int id = v.getId();
56 | for (int i = 0; i < mImageIDs.length; i++) {
57 | if (id == mImageIDs[i]) {
58 | setViewImageAndTag(v, value, defaultImages[i]);
59 | }
60 | }
61 | }
62 |
63 | private void setViewImageAndTag(ImageView v, String value,
64 | Drawable defaultImage) {
65 | v.setImageDrawable(defaultImage);
66 | if (value != null && value.length() > 0) {
67 | v.setTag(R.id.ic__uri, Uri.parse(value));
68 | } else {
69 | v.setTag(R.id.ic__uri, null);
70 | }
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/imagecache/SimpleThumbnailCursorAdapter.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache;
2 |
3 | /*
4 | * Copyright (C) 2011-2012 MIT Mobile Experience Lab
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public
17 | * License along with this library; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 | */
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import android.content.Context;
24 | import android.database.Cursor;
25 | import android.graphics.drawable.Drawable;
26 | import android.net.Uri;
27 | import android.support.v4.widget.SimpleCursorAdapter;
28 | import android.util.SparseArray;
29 | import android.view.LayoutInflater;
30 | import android.view.View;
31 | import android.view.ViewGroup;
32 | import android.widget.ImageView;
33 |
34 | /**
35 | * Like the SimpleCursorAdapter, but has support for setting thumbnails that can
36 | * be loaded using a wrapped {@link ImageLoaderAdapter}.
37 | *
38 | * Multiple image fields can be mapped to the same image view and the first one
39 | * with a valid image will be loaded.
40 | *
41 | * Additionally, it shows an indeterminate progress bar when the adapter is
42 | * null. This progress bar will go away once the adapter has been set.
43 | *
44 | * @author steve
45 | *
46 | */
47 | public class SimpleThumbnailCursorAdapter extends SimpleCursorAdapter {
48 |
49 | private final Drawable defaultImages[];
50 | private final int[] mImageIDs;
51 | // XXX HACK the alternate images system is sorta a hack.
52 | private final SparseArray> mAlternateImages = new SparseArray>();
53 |
54 | private final Context mContext;
55 |
56 | private boolean mShowIndeterminate = false;
57 | private int mExpectedCount = -1;
58 | private View mIndeterminate;
59 |
60 | /**
61 | * All parameters are passed directly to {@link SimpleCursorAdapter}
62 | * {@link #SimpleThumbnailCursorAdapter(Context, int, Cursor, String[], int[], int[], int)}
63 | *
64 | * @param context
65 | * @param layout
66 | * @param c
67 | * @param from
68 | * You can load alternate images by specifying multiple from IDs
69 | * mapping to the same TO ID. This only works for images, as
70 | * listed below.
71 | * @param to
72 | * @param imageIDs
73 | * a list of ImageView IDs whose images will be loaded by this
74 | * adapter.
75 | * @param flags
76 | */
77 | public SimpleThumbnailCursorAdapter(Context context, int layout, Cursor c,
78 | String[] from, int[] to, int[] imageIDs, int flags) {
79 | super(context, layout, c, from, to, flags);
80 |
81 | mContext = context;
82 |
83 | for (final int imageID : imageIDs) {
84 | final List alternates = new ArrayList();
85 | mAlternateImages.put(imageID, alternates);
86 | for (int i = 0; i < to.length; i++) {
87 | if (to[i] == imageID) {
88 | alternates.add(from[i]);
89 | }
90 | }
91 |
92 | setIndeterminateLoading(c == null || isNotShowingExpectedCount(c));
93 | }
94 |
95 | final View v = LayoutInflater.from(context)
96 | .inflate(layout, null, false);
97 | defaultImages = new Drawable[imageIDs.length];
98 |
99 | mImageIDs = imageIDs;
100 |
101 | for (int i = 0; i < mImageIDs.length; i++) {
102 | final ImageView thumb = (ImageView) v.findViewById(imageIDs[i]);
103 | defaultImages[i] = thumb.getDrawable();
104 | }
105 | }
106 |
107 | @Override
108 | public void setViewImage(ImageView v, String value) {
109 | final int id = v.getId();
110 | for (int i = 0; i < mImageIDs.length; i++) {
111 | if (id == mImageIDs[i]) {
112 | final List alternates = mAlternateImages.get(id);
113 | if (alternates != null && alternates.size() > 1) {
114 | final Cursor c = getCursor();
115 | for (final String alternate : alternates) {
116 | final int idx = c.getColumnIndex(alternate);
117 | if (c.isNull(idx)) {
118 | continue;
119 | } else {
120 | // only set the first one that isn't null
121 | setViewImageAndTag(v, c.getString(idx),
122 | defaultImages[i]);
123 | break;
124 | }
125 | }
126 | } else {
127 | setViewImageAndTag(v, value, defaultImages[i]);
128 | }
129 | }
130 | }
131 | }
132 |
133 | private void setViewImageAndTag(ImageView v, String value,
134 | Drawable defaultImage) {
135 | v.setImageDrawable(defaultImage);
136 | if (value != null && value.length() > 0) {
137 | v.setTag(R.id.ic__uri, Uri.parse(value));
138 | } else {
139 | v.setTag(R.id.ic__uri, null);
140 | }
141 | }
142 |
143 | private void setIndeterminateLoading(boolean isLoading) {
144 | if (isLoading != mShowIndeterminate) {
145 | mShowIndeterminate = isLoading;
146 | notifyDataSetInvalidated();
147 | notifyDataSetChanged();
148 | }
149 | }
150 |
151 | @Override
152 | public int getCount() {
153 | if (mShowIndeterminate) {
154 | return 1;
155 | } else {
156 | return super.getCount();
157 | }
158 | }
159 |
160 | @Override
161 | public View getView(int position, View convertView, ViewGroup parent) {
162 | final boolean convertViewIsProgressBar = convertView != null
163 | && convertView.getId() == R.id.progress;
164 |
165 | if (mShowIndeterminate) {
166 | if (convertViewIsProgressBar) {
167 | return convertView;
168 | }
169 |
170 | if (mIndeterminate == null) {
171 | mIndeterminate = LayoutInflater.from(mContext).inflate(R.layout.list_loading,
172 | parent, false);
173 | }
174 |
175 | return mIndeterminate;
176 |
177 | } else {
178 | // ensure that we don't reuse the indeterminate progress bar as a
179 | // real view
180 | if (convertViewIsProgressBar) {
181 | convertView = null;
182 | }
183 | return super.getView(position, convertView, parent);
184 | }
185 | }
186 |
187 | @Override
188 | public Cursor swapCursor(Cursor c) {
189 | setIndeterminateLoading(c == null || isNotShowingExpectedCount(c));
190 | return super.swapCursor(c);
191 | }
192 |
193 | @Override
194 | public void changeCursor(Cursor cursor) {
195 | super.changeCursor(cursor);
196 | setIndeterminateLoading(cursor == null || isNotShowingExpectedCount(cursor));
197 | }
198 |
199 | private boolean isNotShowingExpectedCount(Cursor c){
200 | return c != null && (mExpectedCount > -1 && (c.getCount() != mExpectedCount));
201 | }
202 |
203 | public void setExpectedCount(int expectedCount){
204 | mExpectedCount = expectedCount;
205 | setIndeterminateLoading(mCursor == null || isNotShowingExpectedCount(mCursor));
206 | }
207 |
208 | @Override
209 | public boolean areAllItemsEnabled() {
210 | return false;
211 | }
212 |
213 | @Override
214 | public boolean isEnabled(int position) {
215 |
216 | if (mShowIndeterminate){
217 | return false;
218 | }else{
219 | return super.isEnabled(position);
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/test/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | gen/
3 |
--------------------------------------------------------------------------------
/test/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
17 |
18 |
21 |
22 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/assets/logo_locast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitmel/Android-Image-Cache/b3b093d945b7ddfa3f68078fe553d6f634d29676/test/assets/logo_locast.png
--------------------------------------------------------------------------------
/test/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | android.library.reference.1=..
11 | # Project target.
12 | target=android-16
13 |
--------------------------------------------------------------------------------
/test/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitmel/Android-Image-Cache/b3b093d945b7ddfa3f68078fe553d6f634d29676/test/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/test/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitmel/Android-Image-Cache/b3b093d945b7ddfa3f68078fe553d6f634d29676/test/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/test/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitmel/Android-Image-Cache/b3b093d945b7ddfa3f68078fe553d6f634d29676/test/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/test/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitmel/Android-Image-Cache/b3b093d945b7ddfa3f68078fe553d6f634d29676/test/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/test/res/layout/activity_concurrency_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/test/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/test/res/layout/small_thumbnail_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
--------------------------------------------------------------------------------
/test/res/layout/square_thumbnail_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/test/res/layout/thumbnail_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/test/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ImageCache Test
4 |
5 |
--------------------------------------------------------------------------------
/test/src/edu/mit/mobile/android/imagecache/test/ConcurrencyTest.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache.test;
2 |
3 | import java.io.IOException;
4 | import java.util.Random;
5 |
6 | import org.apache.http.client.ClientProtocolException;
7 |
8 | import android.app.Activity;
9 | import android.content.Context;
10 | import android.graphics.Bitmap.CompressFormat;
11 | import android.net.Uri;
12 | import android.os.Bundle;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.widget.GridView;
16 | import android.widget.Toast;
17 | import edu.mit.mobile.android.imagecache.ImageCache;
18 |
19 | public class ConcurrencyTest extends Activity {
20 |
21 | private GridView mGrid;
22 | private ImageCache mCache;
23 | private TestData mData;
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 |
29 | setContentView(R.layout.activity_concurrency_test);
30 |
31 | mCache = new SlowImageCache(this);
32 |
33 | mGrid = (GridView) findViewById(R.id.grid);
34 |
35 | mData = new TestData();
36 |
37 | initData();
38 |
39 | mGrid.setAdapter(TestData.generateAdapter(this, mData, R.layout.square_thumbnail_item,
40 | mCache, 64, 64));
41 | }
42 |
43 | private void initData() {
44 | mData.addItem(
45 | "Federico",
46 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/person_profile/sites/mel-drudev.mit.edu/files/pic_64px_boss.jpg");
47 | mData.addItem(
48 | "Leo",
49 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/person_profile/sites/mel-drudev.mit.edu/files/leonardo_0.jpg");
50 |
51 | mData.addItem(
52 | "Nick",
53 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/person_profile/nwallen_pic.jpg");
54 |
55 | mData.addItem(
56 | "Steve",
57 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/person_profile/sites/mel-drudev.mit.edu/files/pic_64px_steve.jpg");
58 |
59 | mData.addItem(
60 | "Amar",
61 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/person_profile/me-icon_0.png");
62 |
63 | for (int i = 0; i < 10; i++) {
64 | mData.addAll(mData);
65 | }
66 | }
67 |
68 | private void trim() {
69 | final long trimmed = mCache.trim();
70 | Toast.makeText(this, trimmed + " byte(s) trimmed.", Toast.LENGTH_LONG).show();
71 | }
72 |
73 | private void clear() {
74 | mCache.clear();
75 | Toast.makeText(this, "Cache cleared.", Toast.LENGTH_LONG).show();
76 | }
77 |
78 | @Override
79 | public boolean onOptionsItemSelected(MenuItem item) {
80 | switch (item.getItemId()) {
81 | case R.id.clear:
82 | clear();
83 | return true;
84 |
85 | case R.id.trim:
86 |
87 | trim();
88 | return true;
89 |
90 | default:
91 | return super.onOptionsItemSelected(item);
92 | }
93 | }
94 |
95 | @Override
96 | public boolean onCreateOptionsMenu(Menu menu) {
97 | super.onCreateOptionsMenu(menu);
98 | getMenuInflater().inflate(R.menu.main_menu, menu);
99 | menu.findItem(R.id.grid).setVisible(false);
100 | return true;
101 | }
102 |
103 | private static class SlowImageCache extends ImageCache {
104 |
105 | private final Random r = new Random();
106 |
107 | protected SlowImageCache(Context context) {
108 | super(context, CompressFormat.JPEG, 85);
109 | }
110 |
111 | @Override
112 | protected void downloadImage(String key, Uri uri) throws ClientProtocolException,
113 | IOException {
114 | try {
115 | Thread.sleep(r.nextInt(3000) + 500);
116 | } catch (final InterruptedException e) {
117 |
118 | }
119 | super.downloadImage(key, uri);
120 |
121 | }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/test/src/edu/mit/mobile/android/imagecache/test/ImageCacheJunitTest.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache.test;
2 |
3 | /*
4 | * Copyright (C) 2011-2013 MIT Mobile Experience Lab
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License
8 | * as published by the Free Software Foundation; either version 2
9 | * of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 |
21 | import java.io.File;
22 | import java.io.FileOutputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 |
26 | import org.apache.http.client.ClientProtocolException;
27 |
28 | import android.content.Context;
29 | import android.graphics.Bitmap;
30 | import android.graphics.BitmapFactory;
31 | import android.graphics.drawable.Drawable;
32 | import android.net.Uri;
33 | import android.test.InstrumentationTestCase;
34 | import android.test.suitebuilder.annotation.LargeTest;
35 | import edu.mit.mobile.android.imagecache.ImageCache;
36 | import edu.mit.mobile.android.imagecache.ImageCacheException;
37 |
38 | public class ImageCacheJunitTest extends InstrumentationTestCase {
39 | @SuppressWarnings("unused")
40 | private static final String TAG = ImageCacheJunitTest.class.getSimpleName();
41 |
42 | private ImageCache imc;
43 |
44 | @Override
45 | protected void setUp() throws Exception {
46 | super.setUp();
47 |
48 | imc = ImageCache.getInstance(getInstrumentation().getTargetContext());
49 | }
50 |
51 | public void testPreconditions() {
52 | assertNotNull(imc);
53 | }
54 |
55 | public void testClear() {
56 | assertTrue(imc.clear());
57 | assertEquals(0, imc.getCacheEntryCount());
58 | }
59 |
60 | public void testGetPut() throws IOException {
61 | final Context contextInst = getInstrumentation().getContext();
62 | testClear();
63 |
64 | final String key01 = "foo";
65 | final String key02 = "bar";
66 | Bitmap bmp = BitmapFactory.decodeResource(contextInst.getResources(),
67 | R.drawable.ic_launcher);
68 | assertTrue(bmp.getHeight() > 0);
69 | assertTrue(bmp.getWidth() > 0);
70 |
71 | imc.put(key01, bmp);
72 |
73 | assertEquals(1, imc.getCacheEntryCount());
74 |
75 | Bitmap bmpResult = imc.get(key01);
76 | assertNotNull(bmpResult);
77 |
78 | // check dimensions
79 | assertBitmapEqual(bmp, bmpResult);
80 |
81 | // check contents to ensure it's the same
82 | // TODO
83 |
84 | bmp = BitmapFactory.decodeResource(contextInst.getResources(),
85 | android.R.drawable.ic_dialog_alert);
86 |
87 | assertTrue(bmp.getHeight() > 0);
88 | assertTrue(bmp.getWidth() > 0);
89 |
90 | // call it again, ensure we overwrite
91 | imc.put(key01, bmp);
92 | assertEquals(1, imc.getCacheEntryCount());
93 |
94 | bmpResult = imc.get(key01);
95 | assertNotNull(bmpResult);
96 |
97 | // check dimensions
98 | assertBitmapEqual(bmp, bmpResult);
99 |
100 | // test to make sure an empty result returns null
101 | assertNull(imc.get(key02));
102 |
103 | testClear();
104 | }
105 |
106 | private void assertBitmapMaxSize(int maxExpectedWidth, int maxExpectedHeight, Drawable actual) {
107 | assertTrue(maxExpectedWidth >= actual.getIntrinsicWidth());
108 | assertTrue(maxExpectedHeight >= actual.getIntrinsicHeight());
109 |
110 | }
111 |
112 | private void assertBitmapMinSize(int minExpectedWidth, int minExpectedHeight, Drawable actual) {
113 | assertTrue(minExpectedWidth <= actual.getIntrinsicWidth());
114 | assertTrue(minExpectedHeight <= actual.getIntrinsicHeight());
115 |
116 | }
117 |
118 | private void assertBitmapEqual(Bitmap expected, Bitmap actual) {
119 | assertEquals(expected.getHeight(), actual.getHeight());
120 | assertEquals(expected.getWidth(), actual.getWidth());
121 | }
122 |
123 | static final int LOCAL_SCALE_SIZE = 100;
124 |
125 | /**
126 | * Loads a file from the assets and saves it to a public location.
127 | *
128 | * @return
129 | * @throws IOException
130 | */
131 | private Uri loadLocalFile() throws IOException {
132 |
133 | final String testfile = "logo_locast.png";
134 | final Context contextInst = getInstrumentation().getContext();
135 | final Context context = getInstrumentation().getTargetContext();
136 | final InputStream is = contextInst.getAssets().open(testfile);
137 |
138 | assertNotNull(is);
139 |
140 | final FileOutputStream fos = context.openFileOutput(testfile, Context.MODE_PRIVATE);
141 |
142 | assertNotNull(fos);
143 |
144 | int read = 0;
145 | final byte[] bytes = new byte[1024];
146 |
147 | while ((read = is.read(bytes)) != -1) {
148 | fos.write(bytes, 0, read);
149 | }
150 |
151 | is.close();
152 | fos.close();
153 |
154 | final File outFile = context.getFileStreamPath(testfile);
155 |
156 | final Uri fileUri = Uri.fromFile(outFile);
157 |
158 | assertNotNull(fileUri);
159 | return fileUri;
160 | }
161 |
162 | public void testLocalFileLoad() throws IOException, ImageCacheException {
163 | testClear();
164 |
165 | final Uri fileUri = loadLocalFile();
166 |
167 | final Drawable img = imc.getImage(fileUri, LOCAL_SCALE_SIZE, LOCAL_SCALE_SIZE);
168 |
169 | assertNotNull(img);
170 |
171 | // the thumbnails produced by this aren't precisely the size we request, due to efficiencies
172 | // in decoding the image.
173 | assertBitmapMaxSize(LOCAL_SCALE_SIZE * 2, LOCAL_SCALE_SIZE * 2, img);
174 |
175 | assertBitmapMinSize(LOCAL_SCALE_SIZE / 2, LOCAL_SCALE_SIZE / 2, img);
176 |
177 | }
178 |
179 | @LargeTest
180 | public void testTrim() throws IOException, ImageCacheException {
181 | testClear();
182 |
183 | final Uri localFile = loadLocalFile();
184 |
185 | imc.setAutoTrimFrequency(0);
186 |
187 | final int maxSize = 150;
188 | final int minSize = 50;
189 | final int entryCount = maxSize - minSize + 1 /* includes max size */;
190 |
191 | for (int i = minSize; i <= maxSize; i++) {
192 | final Drawable img = imc.getImage(localFile, i, i);
193 |
194 | assertNotNull(img);
195 | }
196 |
197 | assertEquals(entryCount, imc.getCacheEntryCount());
198 |
199 | // cause a cache hit on the first item.
200 | imc.get(imc.getKey(localFile, minSize, minSize));
201 |
202 | final long diskUsage = imc.getCacheDiskUsage();
203 |
204 | assertTrue("Disk usage isn't reasonable", diskUsage > 1000 && diskUsage < 10 * 1024 * 1024);
205 |
206 | // actual disk usage should be around 479100
207 |
208 | final long cacheSize = 300 * 1024 /* kilo */;
209 | imc.setCacheMaxSize(cacheSize);
210 |
211 | final long trimmed = imc.trim();
212 |
213 | assertTrue("no bytes were trimmed", trimmed > 0);
214 |
215 | assertTrue("disk usage hasn't changed", diskUsage != imc.getCacheDiskUsage());
216 |
217 | assertTrue("disk usage is larger than desired max size",
218 | imc.getCacheDiskUsage() < cacheSize);
219 |
220 | assertTrue("entry count wasn't reduced", imc.getCacheEntryCount() < entryCount);
221 |
222 | // this should have the earliest access time, so it should be trimmed first
223 | assertFalse("second entry wasn't trimmed",
224 | imc.contains(imc.getKey(localFile, minSize + 1, minSize + 1)));
225 |
226 | // this has the most recent creation date, so it should be trimmed last
227 | assertTrue("last entry was trimmed", imc.contains(imc.getKey(localFile, maxSize, maxSize)));
228 |
229 |
230 | }
231 |
232 | private final int NET_SCALE_SIZE = 100;
233 |
234 | private void testNetworkLoad(Uri uri) throws IOException, ImageCacheException {
235 |
236 | // ensure we don't have it in the cache
237 | final String origKey = imc.getKey(uri);
238 | assertNull(imc.getDrawable(origKey));
239 |
240 | final String scaledKey = imc.getKey(uri, NET_SCALE_SIZE, NET_SCALE_SIZE);
241 | assertNull(imc.getDrawable(scaledKey));
242 |
243 | final Drawable img = imc.getImage(uri, NET_SCALE_SIZE, NET_SCALE_SIZE);
244 |
245 | assertNotNull(img);
246 |
247 | assertBitmapMaxSize(NET_SCALE_SIZE * 2, NET_SCALE_SIZE * 2, img);
248 |
249 | assertBitmapMinSize(NET_SCALE_SIZE / 2, NET_SCALE_SIZE / 2, img);
250 |
251 | // ensure that it's stored in the disk cache
252 | assertNotNull(imc.get(origKey));
253 | assertNotNull(imc.get(scaledKey));
254 |
255 | }
256 |
257 | public void testNetworkLoad() throws ClientProtocolException, IOException, ImageCacheException {
258 | testClear();
259 |
260 | testNetworkLoad(Uri.parse("http://mobile-server.mit.edu/~stevep/logo_start_locast1.png"));
261 | }
262 |
263 | public void testNetworkLoadLarge() throws ClientProtocolException, IOException,
264 | ImageCacheException {
265 | testClear();
266 |
267 | testNetworkLoad(Uri.parse("http://mobile-server.mit.edu/~stevep/large_logo.png"));
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/test/src/edu/mit/mobile/android/imagecache/test/InteractiveDemo.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache.test;
2 |
3 | /*
4 | * Copyright (C) 2011-2013 MIT Mobile Experience Lab
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License
8 | * as published by the Free Software Foundation; either version 2
9 | * of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | import android.app.ListActivity;
21 | import android.content.Intent;
22 | import android.os.Bundle;
23 | import android.view.Menu;
24 | import android.view.MenuItem;
25 | import android.widget.Gallery;
26 | import android.widget.Toast;
27 | import edu.mit.mobile.android.imagecache.ImageCache;
28 |
29 | @SuppressWarnings("deprecation")
30 | public class InteractiveDemo extends ListActivity {
31 | /** Called when the activity is first created. */
32 | private ImageCache mCache;
33 |
34 | private final TestData mTestData = new TestData();
35 |
36 | @Override
37 | public void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.main);
40 |
41 | final Gallery gallery = (Gallery) findViewById(R.id.gallery);
42 |
43 | mCache = ImageCache.getInstance(this);
44 | mCache.setCacheMaxSize(1 * 1024 /* mega */* 1024 /* kilo */);
45 |
46 | initData();
47 |
48 | setListAdapter(TestData.generateAdapter(this, mTestData, R.layout.thumbnail_item, mCache,
49 | 320, 200));
50 |
51 | gallery.setAdapter(TestData.generateAdapter(this, mTestData, R.layout.small_thumbnail_item,
52 | mCache, 160, 100));
53 | }
54 |
55 | private void initData() {
56 |
57 | mTestData
58 | .addItem(
59 | "locast tourism",
60 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/locast_tourism.jpg");
61 | mTestData
62 | .addItem(
63 | "green home",
64 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/gha_01.jpg");
65 | mTestData
66 | .addItem(
67 | "green home 2",
68 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/gha_05.jpg");
69 | mTestData
70 | .addItem(
71 | "Locast healthcare",
72 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/locast%20healthcare.jpg");
73 | mTestData
74 | .addItem(
75 | "locast h2flow 1",
76 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/water%20project%20IMAGE-72dpi.jpg");
77 | mTestData
78 | .addItem(
79 | "locast h2flow 2",
80 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/H2flOw_image1.jpg");
81 | mTestData
82 | .addItem(
83 | "locast h2flow 3",
84 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/H2flOw_image2.jpg");
85 | mTestData
86 | .addItem(
87 | "locast unicef 1",
88 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/Screen%20shot%202011-08-16%20at%204.05.57%20PM.png");
89 | mTestData
90 | .addItem(
91 | "locast unicef 2",
92 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/Screen%20shot%202011-08-16%20at%204.14.00%20PM.png");
93 | mTestData
94 | .addItem(
95 | "locast unicef 3",
96 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/DSC01492.JPG");
97 | mTestData
98 | .addItem(
99 | "memory traces",
100 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/North%20end%20memory.jpg");
101 | mTestData
102 | .addItem(
103 | "uv tracking",
104 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/MEL_LocastProjectsandNextTV2%2031_1.jpg");
105 | mTestData
106 | .addItem(
107 | "civic media 1",
108 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/sites/mel-drudev.mit.edu/files/locast_civic_00.jpg");
109 | mTestData
110 | .addItem(
111 | "civic media 2",
112 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/locastPA2.jpg");
113 | mTestData
114 | .addItem(
115 | "civic media 3",
116 | "http://mobile.mit.edu/sites/mel-dru.mit.edu.mainsite/files/imagecache/implementation_big/locastPA3.jpg");
117 |
118 | // fill it up!
119 | mTestData.addAll(mTestData);
120 | mTestData.addAll(mTestData);
121 |
122 | }
123 |
124 | private void trim() {
125 | final long trimmed = mCache.trim();
126 | Toast.makeText(this, trimmed + " byte(s) trimmed.", Toast.LENGTH_LONG).show();
127 | }
128 |
129 | private void clear() {
130 | mCache.clear();
131 | Toast.makeText(this, "Cache cleared.", Toast.LENGTH_LONG).show();
132 | }
133 |
134 | @Override
135 | public boolean onOptionsItemSelected(MenuItem item) {
136 | switch (item.getItemId()) {
137 | case R.id.clear:
138 | clear();
139 | return true;
140 |
141 | case R.id.trim:
142 |
143 | trim();
144 | return true;
145 |
146 | case R.id.grid:
147 | startActivity(new Intent(this, ConcurrencyTest.class));
148 | return true;
149 |
150 | default:
151 | return super.onOptionsItemSelected(item);
152 | }
153 | }
154 |
155 | @Override
156 | public boolean onCreateOptionsMenu(Menu menu) {
157 | super.onCreateOptionsMenu(menu);
158 | getMenuInflater().inflate(R.menu.main_menu, menu);
159 | return true;
160 | }
161 |
162 | }
--------------------------------------------------------------------------------
/test/src/edu/mit/mobile/android/imagecache/test/KeyedLockTest.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache.test;
2 |
3 | import java.util.Collections;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | import android.test.AndroidTestCase;
8 | import android.util.Log;
9 | import edu.mit.mobile.android.imagecache.KeyedLock;
10 |
11 | public class KeyedLockTest extends AndroidTestCase {
12 |
13 | protected static final String TAG = "KeyedLockTest";
14 | final KeyedLock mLock = new KeyedLock();
15 |
16 | private final Set mGotFromStore = Collections.synchronizedSet(new HashSet());
17 |
18 | public void testKeyedLock() throws InterruptedException {
19 |
20 | final Thread t1 = new Thread(new Sandwich());
21 | final Thread t2 = new Thread(new Sandwich());
22 | final Thread t3 = new Thread(new Sandwich());
23 |
24 | t1.start();
25 | t2.start();
26 | t3.start();
27 |
28 | t1.join();
29 | t2.join();
30 | t3.join();
31 | }
32 |
33 | public void getIngredient(String ingredient) {
34 | mLock.lock(ingredient);
35 |
36 | log("Getting " + ingredient);
37 |
38 | try {
39 | if (mGotFromStore.contains(ingredient)) {
40 | log("Already have " + ingredient);
41 | return;
42 | }
43 |
44 | log("Going to the store to get " + ingredient);
45 |
46 | try {
47 | Thread.sleep(1000);
48 | } catch (final InterruptedException e) {
49 | log("interrupted going to the store");
50 | }
51 | mGotFromStore.add(ingredient);
52 |
53 | log("Returned from the store with " + ingredient);
54 | } finally {
55 | mLock.unlock(ingredient);
56 | }
57 | }
58 |
59 | private static int mId = 0;
60 |
61 | private static long startTime = System.nanoTime();
62 |
63 | private long getTime() {
64 | return (System.nanoTime() - startTime) / 1000000;
65 | }
66 |
67 | private void log(String msg) {
68 | Log.d(TAG, "(" + getTime() + "\t" + Thread.currentThread().getId() + "): " + msg);
69 | }
70 |
71 | private class Sandwich implements Runnable {
72 |
73 | int id = mId++;
74 |
75 | @Override
76 | public void run() {
77 | log("Going to make a sandwich! I need bread...");
78 |
79 | getIngredient("bread");
80 | log("got bread");
81 |
82 | getIngredient("cheese");
83 | log("got cheese");
84 |
85 | }
86 |
87 | private void log(String msg) {
88 | KeyedLockTest.this.log("#" + id + ": " + msg);
89 | }
90 |
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/test/src/edu/mit/mobile/android/imagecache/test/TestData.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.imagecache.test;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 |
6 | import android.content.Context;
7 | import edu.mit.mobile.android.imagecache.ImageCache;
8 | import edu.mit.mobile.android.imagecache.ImageLoaderAdapter;
9 | import edu.mit.mobile.android.imagecache.SimpleThumbnailAdapter;
10 |
11 | public class TestData extends ArrayList> {
12 |
13 | /**
14 | *
15 | */
16 | private static final long serialVersionUID = -6862061777028855417L;
17 |
18 | public void addItem(String title, String image) {
19 | final HashMap m = new HashMap();
20 |
21 | m.put("title", title);
22 | m.put("thumb", image);
23 |
24 | add(m);
25 | }
26 |
27 | public static ImageLoaderAdapter generateAdapter(Context context, TestData data, int layout,
28 | ImageCache cache, int width, int height) {
29 | final SimpleThumbnailAdapter bigAdapter = new SimpleThumbnailAdapter(context, data,
30 | layout,
31 | new String[] { "thumb" }, new int[] { R.id.thumb },
32 | new int[] { R.id.thumb });
33 |
34 | return new ImageLoaderAdapter(context, bigAdapter, cache, new int[] { R.id.thumb }, width,
35 | height, ImageLoaderAdapter.UNIT_DIP);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------