/*
* 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<PhotoProvider> {
@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, null);
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, null);
assertTrue(mReady.waitForNotification());
assertNull(mReady.mInputStream);
assertNotNull(mReady.mOriginalFile);
mReady = new ReadyCollector();
mMediaCache.retrievePreview(uri, mReady, null);
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, null);
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, null);
mReady.waitForNotification();
mReady.mInputStream.close();
mMediaCache.retrieveOriginal(uri, mReady, null);
assertTrue(mReady.waitForNotification());
assertNotNull(mReady.mInputStream);
mReady.mInputStream.close();
}
public void testBadRetriever() {
Uri uri = Photos.CONTENT_URI;
try {
mMediaCache.retrieveOriginal(uri, mReady, null);
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, null);
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, null);
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();
}
}