From 314de4034664ab668aea61e1ecd14946f1b4a978 Mon Sep 17 00:00:00 2001 From: George Mount Date: Mon, 18 Mar 2013 09:51:21 -0700 Subject: Add initial implementation of MediaCache. Change-Id: I94d357bab0e57cc60b8be790d912ba036730298b --- .../com/android/photos/data/DataTestRunner.java | 10 + .../com/android/photos/data/MediaCacheTest.java | 388 +++++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 tests/src/com/android/photos/data/MediaCacheTest.java (limited to 'tests/src') diff --git a/tests/src/com/android/photos/data/DataTestRunner.java b/tests/src/com/android/photos/data/DataTestRunner.java index 432258561..10618d67d 100644 --- a/tests/src/com/android/photos/data/DataTestRunner.java +++ b/tests/src/com/android/photos/data/DataTestRunner.java @@ -18,6 +18,9 @@ package com.android.photos.data; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestSuite; +import com.android.photos.data.TestHelper.TestInitialization; + +import junit.framework.TestCase; import junit.framework.TestSuite; public class DataTestRunner extends InstrumentationTestRunner { @@ -26,6 +29,13 @@ public class DataTestRunner extends InstrumentationTestRunner { TestSuite suite = new InstrumentationTestSuite(this); suite.addTestSuite(PhotoDatabaseTest.class); suite.addTestSuite(PhotoProviderTest.class); + TestHelper.addTests(MediaCacheTest.class, suite, new TestInitialization() { + @Override + public void initialize(TestCase testCase) { + MediaCacheTest test = (MediaCacheTest) testCase; + test.setLocalContext(getContext()); + } + }); return suite; } diff --git a/tests/src/com/android/photos/data/MediaCacheTest.java b/tests/src/com/android/photos/data/MediaCacheTest.java new file mode 100644 index 000000000..df990edb1 --- /dev/null +++ b/tests/src/com/android/photos/data/MediaCacheTest.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.photos.data; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.SystemClock; +import android.test.ProviderTestCase2; + +import com.android.gallery3d.tests.R; +import com.android.photos.data.MediaCache.ImageReady; +import com.android.photos.data.MediaCache.OriginalReady; +import com.android.photos.data.MediaRetriever.MediaSize; +import com.android.photos.data.PhotoProvider.Photos; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class MediaCacheTest extends ProviderTestCase2 { + @SuppressWarnings("unused") + private static final String TAG = MediaCacheTest.class.getSimpleName(); + + private File mDir; + private File mImage; + private File mCacheDir; + private Resources mResources; + private MediaCache mMediaCache; + private ReadyCollector mReady; + + public static final long MAX_WAIT = 2000; + + private static class ReadyCollector implements ImageReady, OriginalReady { + public File mOriginalFile; + public InputStream mInputStream; + + @Override + public synchronized void originalReady(File originalFile) { + mOriginalFile = originalFile; + notifyAll(); + } + + @Override + public synchronized void imageReady(InputStream bitmapInputStream) { + mInputStream = bitmapInputStream; + notifyAll(); + } + + public synchronized boolean waitForNotification() { + long endWait = SystemClock.uptimeMillis() + MAX_WAIT; + + try { + while (mInputStream == null && mOriginalFile == null + && SystemClock.uptimeMillis() < endWait) { + wait(endWait - SystemClock.uptimeMillis()); + } + } catch (InterruptedException e) { + } + return mInputStream != null || mOriginalFile != null; + } + } + + private static class DummyMediaRetriever implements MediaRetriever { + private boolean mNullUri = false; + @Override + public File getLocalFile(Uri contentUri) { + return null; + } + + @Override + public MediaSize getFastImageSize(Uri contentUri, MediaSize size) { + return null; + } + + @Override + public byte[] getTemporaryImage(Uri contentUri, MediaSize temporarySize) { + return null; + } + + @Override + public boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile) { + return false; + } + + @Override + public Uri normalizeUri(Uri contentUri, MediaSize size) { + if (mNullUri) { + return null; + } else { + return contentUri; + } + } + + @Override + public MediaSize normalizeMediaSize(Uri contentUri, MediaSize size) { + return size; + } + + public void setNullUri() { + mNullUri = true; + } + }; + + public MediaCacheTest() { + super(PhotoProvider.class, PhotoProvider.AUTHORITY); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + mReady = new ReadyCollector(); + File externalDir = Environment.getExternalStorageDirectory(); + mDir = new File(externalDir, "test"); + mDir.mkdirs(); + mCacheDir = new File(externalDir, "test_cache"); + mImage = new File(mDir, "original.jpg"); + MediaCache.initialize(getMockContext()); + MediaCache.getInstance().setCacheDir(mCacheDir); + mMediaCache = MediaCache.getInstance(); + mMediaCache.addRetriever("file", "", new FileRetriever()); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mMediaCache.clearCacheDir(); + MediaCache.shutdown(); + mMediaCache = null; + mImage.delete(); + mDir.delete(); + mCacheDir.delete(); + } + + public void setLocalContext(Context context) { + mResources = context.getResources(); + } + + public void testRetrieveOriginal() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + mMediaCache.retrieveOriginal(uri, mReady, mReady); + assertTrue(mReady.waitForNotification()); + assertNull(mReady.mInputStream); + assertEquals(mImage, mReady.mOriginalFile); + } + + public void testRetrievePreview() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + mMediaCache.retrievePreview(uri, mReady, null); + assertTrue(mReady.waitForNotification()); + assertNotNull(mReady.mInputStream); + assertNull(mReady.mOriginalFile); + Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream); + mReady.mInputStream.close(); + assertNotNull(bitmap); + Bitmap original = BitmapFactory.decodeFile(mImage.getPath()); + assertTrue(bitmap.getWidth() < original.getWidth()); + assertTrue(bitmap.getHeight() < original.getHeight()); + int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Preview); + assertTrue(maxDimension >= targetSize); + assertTrue(maxDimension < (targetSize * 2)); + } + + public void testRetrieveExifThumb() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + ReadyCollector done = new ReadyCollector(); + mMediaCache.retrieveThumbnail(uri, done, mReady); + assertTrue(mReady.waitForNotification()); + assertNotNull(mReady.mInputStream); + assertNull(mReady.mOriginalFile); + Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream); + mReady.mInputStream.close(); + assertTrue(done.waitForNotification()); + assertNotNull(done.mInputStream); + done.mInputStream.close(); + assertNotNull(bitmap); + assertEquals(320, bitmap.getWidth()); + assertEquals(240, bitmap.getHeight()); + } + + public void testRetrieveThumb() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + long downsampleStart = SystemClock.uptimeMillis(); + mMediaCache.retrieveThumbnail(uri, mReady, null); + assertTrue(mReady.waitForNotification()); + long downsampleEnd = SystemClock.uptimeMillis(); + assertNotNull(mReady.mInputStream); + assertNull(mReady.mOriginalFile); + Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream); + mReady.mInputStream.close(); + assertNotNull(bitmap); + Bitmap original = BitmapFactory.decodeFile(mImage.getPath()); + assertTrue(bitmap.getWidth() < original.getWidth()); + assertTrue(bitmap.getHeight() < original.getHeight()); + int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Thumbnail); + assertTrue(maxDimension >= targetSize); + assertTrue(maxDimension < (targetSize * 2)); + + // Retrieve cached thumb. + mReady = new ReadyCollector(); + long start = SystemClock.uptimeMillis(); + mMediaCache.retrieveThumbnail(uri, mReady, null); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + long end = SystemClock.uptimeMillis(); + // Already cached. Wait shorter time. + assertTrue((end - start) < (downsampleEnd - downsampleStart) / 2); + } + + public void testGetVideo() throws IOException { + mImage = new File(mDir, "original.mp4"); + copyResourceToFile(R.raw.android_lawn, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + + mMediaCache.retrieveOriginal(uri, mReady, mReady); + assertTrue(mReady.waitForNotification()); + assertNull(mReady.mInputStream); + assertNotNull(mReady.mOriginalFile); + + mReady = new ReadyCollector(); + mMediaCache.retrievePreview(uri, mReady, mReady); + assertTrue(mReady.waitForNotification()); + assertNotNull(mReady.mInputStream); + assertNull(mReady.mOriginalFile); + Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream); + mReady.mInputStream.close(); + int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Preview); + assertTrue(maxDimension >= targetSize); + assertTrue(maxDimension < (targetSize * 2)); + + mReady = new ReadyCollector(); + mMediaCache.retrieveThumbnail(uri, mReady, mReady); + assertTrue(mReady.waitForNotification()); + assertNotNull(mReady.mInputStream); + assertNull(mReady.mOriginalFile); + bitmap = BitmapFactory.decodeStream(mReady.mInputStream); + mReady.mInputStream.close(); + maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + targetSize = MediaCacheUtils.getTargetSize(MediaSize.Thumbnail); + assertTrue(maxDimension >= targetSize); + assertTrue(maxDimension < (targetSize * 2)); + } + + public void testFastImage() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + mMediaCache.retrieveThumbnail(uri, mReady, mReady); + mReady.waitForNotification(); + mReady.mInputStream.close(); + + mMediaCache.retrieveOriginal(uri, mReady, mReady); + assertTrue(mReady.waitForNotification()); + assertNotNull(mReady.mInputStream); + mReady.mInputStream.close(); + } + + public void testBadRetriever() { + Uri uri = Photos.CONTENT_URI; + try { + mMediaCache.retrieveOriginal(uri, mReady, mReady); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testInsertIntoCache() throws IOException { + // FileRetriever inserts into the cache opportunistically with Videos + mImage = new File(mDir, "original.mp4"); + copyResourceToFile(R.raw.android_lawn, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + + mMediaCache.retrieveThumbnail(uri, mReady, mReady); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Preview)); + } + + public void testBadNormalizedUri() { + DummyMediaRetriever retriever = new DummyMediaRetriever(); + Uri uri = Uri.fromParts("http", "world", "morestuff"); + mMediaCache.addRetriever(uri.getScheme(), uri.getAuthority(), retriever); + retriever.setNullUri(); + try { + mMediaCache.retrieveOriginal(uri, mReady, mReady); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testClearOldCache() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri uri = Uri.fromFile(mImage); + mMediaCache.retrievePreview(uri, mReady, null); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + mMediaCache.setMaxCacheSize(mMediaCache.getCachedFile(uri, MediaSize.Preview).length()); + assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Preview)); + + mReady = new ReadyCollector(); + // This should kick the preview image out of the cache. + mMediaCache.retrieveThumbnail(uri, mReady, null); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + assertNull(mMediaCache.getCachedFile(uri, MediaSize.Preview)); + assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Thumbnail)); + } + + public void testClearLargeInCache() throws IOException { + copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath()); + Uri imageUri = Uri.fromFile(mImage); + mMediaCache.retrieveThumbnail(imageUri, mReady, null); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail)); + long thumbSize = mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail).length(); + mMediaCache.setMaxCacheSize(thumbSize * 10); + + for (int i = 0; i < 9; i++) { + File tempImage = new File(mDir, "image" + i + ".jpg"); + mImage.renameTo(tempImage); + Uri tempImageUri = Uri.fromFile(tempImage); + mReady = new ReadyCollector(); + mMediaCache.retrieveThumbnail(tempImageUri, mReady, null); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + tempImage.renameTo(mImage); + } + assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail)); + + for (int i = 0; i < 9; i++) { + File tempImage = new File(mDir, "image" + i + ".jpg"); + mImage.renameTo(tempImage); + Uri tempImageUri = Uri.fromFile(tempImage); + mReady = new ReadyCollector(); + mMediaCache.retrievePreview(tempImageUri, mReady, null); + assertTrue(mReady.waitForNotification()); + mReady.mInputStream.close(); + tempImage.renameTo(mImage); + } + assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail)); + Uri oldestUri = Uri.fromFile(new File(mDir, "image0.jpg")); + assertNull(mMediaCache.getCachedFile(oldestUri, MediaSize.Thumbnail)); + } + + private void copyResourceToFile(int resourceId, String path) throws IOException { + File outputDir = new File(path).getParentFile(); + outputDir.mkdirs(); + + InputStream in = mResources.openRawResource(resourceId); + FileOutputStream out = new FileOutputStream(path); + byte[] buffer = new byte[1000]; + int bytesRead; + + while ((bytesRead = in.read(buffer)) >= 0) { + out.write(buffer, 0, bytesRead); + } + + in.close(); + out.close(); + } +} -- cgit v1.2.3