summaryrefslogtreecommitdiffstats
path: root/src/com/android/photos/data/MediaCache.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/photos/data/MediaCache.java')
-rw-r--r--src/com/android/photos/data/MediaCache.java676
1 files changed, 0 insertions, 676 deletions
diff --git a/src/com/android/photos/data/MediaCache.java b/src/com/android/photos/data/MediaCache.java
deleted file mode 100644
index 0952a4017..000000000
--- a/src/com/android/photos/data/MediaCache.java
+++ /dev/null
@@ -1,676 +0,0 @@
-/*
- * 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.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Environment;
-import android.util.Log;
-
-import com.android.photos.data.MediaCacheDatabase.Action;
-import com.android.photos.data.MediaRetriever.MediaSize;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-
-/**
- * MediaCache keeps a cache of images, videos, thumbnails and previews. Calls to
- * retrieve a specific media item are executed asynchronously. The caller has an
- * option to receive a notification for lower resolution images that happen to
- * be available prior to the one requested.
- * <p>
- * When an media item has been retrieved, the notification for it is called on a
- * separate notifier thread. This thread should not be held for a long time so
- * that other notifications may happen.
- * </p>
- * <p>
- * Media items are uniquely identified by their content URIs. Each
- * scheme/authority can offer its own MediaRetriever, running in its own thread.
- * </p>
- * <p>
- * The MediaCache is an LRU cache, but does not allow the thumbnail cache to
- * drop below a minimum size. This prevents browsing through original images to
- * wipe out the thumbnails.
- * </p>
- */
-public class MediaCache {
- static final String TAG = MediaCache.class.getSimpleName();
- /** Subdirectory containing the image cache. */
- static final String IMAGE_CACHE_SUBDIR = "image_cache";
- /** File name extension to use for cached images. */
- static final String IMAGE_EXTENSION = ".cache";
- /** File name extension to use for temporary cached images while retrieving. */
- static final String TEMP_IMAGE_EXTENSION = ".temp";
-
- public static interface ImageReady {
- void imageReady(InputStream bitmapInputStream);
- }
-
- public static interface OriginalReady {
- void originalReady(File originalFile);
- }
-
- /** A Thread for each MediaRetriever */
- private class ProcessQueue extends Thread {
- private Queue<ProcessingJob> mQueue;
-
- public ProcessQueue(Queue<ProcessingJob> queue) {
- mQueue = queue;
- }
-
- @Override
- public void run() {
- while (mRunning) {
- ProcessingJob status;
- synchronized (mQueue) {
- while (mQueue.isEmpty()) {
- try {
- mQueue.wait();
- } catch (InterruptedException e) {
- if (!mRunning) {
- return;
- }
- Log.w(TAG, "Unexpected interruption", e);
- }
- }
- status = mQueue.remove();
- }
- processTask(status);
- }
- }
- };
-
- private interface NotifyReady {
- void notifyReady();
-
- void setFile(File file) throws FileNotFoundException;
-
- boolean isPrefetch();
- }
-
- private static class NotifyOriginalReady implements NotifyReady {
- private final OriginalReady mCallback;
- private File mFile;
-
- public NotifyOriginalReady(OriginalReady callback) {
- mCallback = callback;
- }
-
- @Override
- public void notifyReady() {
- if (mCallback != null) {
- mCallback.originalReady(mFile);
- }
- }
-
- @Override
- public void setFile(File file) {
- mFile = file;
- }
-
- @Override
- public boolean isPrefetch() {
- return mCallback == null;
- }
- }
-
- private static class NotifyImageReady implements NotifyReady {
- private final ImageReady mCallback;
- private InputStream mInputStream;
-
- public NotifyImageReady(ImageReady callback) {
- mCallback = callback;
- }
-
- @Override
- public void notifyReady() {
- if (mCallback != null) {
- mCallback.imageReady(mInputStream);
- }
- }
-
- @Override
- public void setFile(File file) throws FileNotFoundException {
- mInputStream = new FileInputStream(file);
- }
-
- public void setBytes(byte[] bytes) {
- mInputStream = new ByteArrayInputStream(bytes);
- }
-
- @Override
- public boolean isPrefetch() {
- return mCallback == null;
- }
- }
-
- /** A media item to be retrieved and its notifications. */
- private static class ProcessingJob {
- public ProcessingJob(Uri uri, MediaSize size, NotifyReady complete,
- NotifyImageReady lowResolution) {
- this.contentUri = uri;
- this.size = size;
- this.complete = complete;
- this.lowResolution = lowResolution;
- }
- public Uri contentUri;
- public MediaSize size;
- public NotifyImageReady lowResolution;
- public NotifyReady complete;
- }
-
- private boolean mRunning = true;
- private static MediaCache sInstance;
- private File mCacheDir;
- private Context mContext;
- private Queue<NotifyReady> mCallbacks = new LinkedList<NotifyReady>();
- private Map<String, MediaRetriever> mRetrievers = new HashMap<String, MediaRetriever>();
- private Map<String, List<ProcessingJob>> mTasks = new HashMap<String, List<ProcessingJob>>();
- private List<ProcessQueue> mProcessingThreads = new ArrayList<ProcessQueue>();
- private MediaCacheDatabase mDatabaseHelper;
- private long mTempImageNumber = 1;
- private Object mTempImageNumberLock = new Object();
-
- private long mMaxCacheSize = 40 * 1024 * 1024; // 40 MB
- private long mMinThumbCacheSize = 4 * 1024 * 1024; // 4 MB
- private long mCacheSize = -1;
- private long mThumbCacheSize = -1;
- private Object mCacheSizeLock = new Object();
-
- private Action mNotifyCachedLowResolution = new Action() {
- @Override
- public void execute(Uri uri, long id, MediaSize size, Object parameter) {
- ProcessingJob job = (ProcessingJob) parameter;
- File file = createCacheImagePath(id);
- addNotification(job.lowResolution, file);
- }
- };
-
- private Action mMoveTempToCache = new Action() {
- @Override
- public void execute(Uri uri, long id, MediaSize size, Object parameter) {
- File tempFile = (File) parameter;
- File cacheFile = createCacheImagePath(id);
- tempFile.renameTo(cacheFile);
- }
- };
-
- private Action mDeleteFile = new Action() {
- @Override
- public void execute(Uri uri, long id, MediaSize size, Object parameter) {
- File file = createCacheImagePath(id);
- file.delete();
- synchronized (mCacheSizeLock) {
- if (mCacheSize != -1) {
- long length = (Long) parameter;
- mCacheSize -= length;
- if (size == MediaSize.Thumbnail) {
- mThumbCacheSize -= length;
- }
- }
- }
- }
- };
-
- /** The thread used to make ImageReady and OriginalReady callbacks. */
- private Thread mProcessNotifications = new Thread() {
- @Override
- public void run() {
- while (mRunning) {
- NotifyReady notifyImage;
- synchronized (mCallbacks) {
- while (mCallbacks.isEmpty()) {
- try {
- mCallbacks.wait();
- } catch (InterruptedException e) {
- if (!mRunning) {
- return;
- }
- Log.w(TAG, "Unexpected Interruption, continuing");
- }
- }
- notifyImage = mCallbacks.remove();
- }
-
- notifyImage.notifyReady();
- }
- }
- };
-
- public static synchronized void initialize(Context context) {
- if (sInstance == null) {
- sInstance = new MediaCache(context);
- MediaCacheUtils.initialize(context);
- }
- }
-
- public static MediaCache getInstance() {
- return sInstance;
- }
-
- public static synchronized void shutdown() {
- sInstance.mRunning = false;
- sInstance.mProcessNotifications.interrupt();
- for (ProcessQueue processingThread : sInstance.mProcessingThreads) {
- processingThread.interrupt();
- }
- sInstance = null;
- }
-
- private MediaCache(Context context) {
- mDatabaseHelper = new MediaCacheDatabase(context);
- mProcessNotifications.start();
- mContext = context;
- }
-
- // This is used for testing.
- public void setCacheDir(File cacheDir) {
- cacheDir.mkdirs();
- mCacheDir = cacheDir;
- }
-
- public File getCacheDir() {
- synchronized (mContext) {
- if (mCacheDir == null) {
- String state = Environment.getExternalStorageState();
- File baseDir;
- if (Environment.MEDIA_MOUNTED.equals(state)) {
- baseDir = mContext.getExternalCacheDir();
- } else {
- // Stored in internal cache
- baseDir = mContext.getCacheDir();
- }
- mCacheDir = new File(baseDir, IMAGE_CACHE_SUBDIR);
- mCacheDir.mkdirs();
- }
- return mCacheDir;
- }
- }
-
- /**
- * Invalidates all cached images related to a given contentUri. This call
- * doesn't complete until the images have been removed from the cache.
- */
- public void invalidate(Uri contentUri) {
- mDatabaseHelper.delete(contentUri, mDeleteFile);
- }
-
- public void clearCacheDir() {
- File[] cachedFiles = getCacheDir().listFiles();
- if (cachedFiles != null) {
- for (File cachedFile : cachedFiles) {
- cachedFile.delete();
- }
- }
- }
-
- /**
- * Add a MediaRetriever for a Uri scheme and authority. This MediaRetriever
- * will be granted its own thread for retrieving images.
- */
- public void addRetriever(String scheme, String authority, MediaRetriever retriever) {
- String differentiator = getDifferentiator(scheme, authority);
- synchronized (mRetrievers) {
- mRetrievers.put(differentiator, retriever);
- }
- synchronized (mTasks) {
- LinkedList<ProcessingJob> queue = new LinkedList<ProcessingJob>();
- mTasks.put(differentiator, queue);
- new ProcessQueue(queue).start();
- }
- }
-
- /**
- * Retrieves a thumbnail. complete will be called when the thumbnail is
- * available. If lowResolution is not null and a lower resolution thumbnail
- * is available before the thumbnail, lowResolution will be called prior to
- * complete. All callbacks will be made on a thread other than the calling
- * thread.
- *
- * @param contentUri The URI for the full resolution image to search for.
- * @param complete Callback for when the image has been retrieved.
- * @param lowResolution If not null and a lower resolution image is
- * available prior to retrieving the thumbnail, this will be
- * called with the low resolution bitmap.
- */
- public void retrieveThumbnail(Uri contentUri, ImageReady complete, ImageReady lowResolution) {
- addTask(contentUri, complete, lowResolution, MediaSize.Thumbnail);
- }
-
- /**
- * Retrieves a preview. complete will be called when the preview is
- * available. If lowResolution is not null and a lower resolution preview is
- * available before the preview, lowResolution will be called prior to
- * complete. All callbacks will be made on a thread other than the calling
- * thread.
- *
- * @param contentUri The URI for the full resolution image to search for.
- * @param complete Callback for when the image has been retrieved.
- * @param lowResolution If not null and a lower resolution image is
- * available prior to retrieving the preview, this will be called
- * with the low resolution bitmap.
- */
- public void retrievePreview(Uri contentUri, ImageReady complete, ImageReady lowResolution) {
- addTask(contentUri, complete, lowResolution, MediaSize.Preview);
- }
-
- /**
- * Retrieves the original image or video. complete will be called when the
- * media is available on the local file system. If lowResolution is not null
- * and a lower resolution preview is available before the original,
- * lowResolution will be called prior to complete. All callbacks will be
- * made on a thread other than the calling thread.
- *
- * @param contentUri The URI for the full resolution image to search for.
- * @param complete Callback for when the image has been retrieved.
- * @param lowResolution If not null and a lower resolution image is
- * available prior to retrieving the preview, this will be called
- * with the low resolution bitmap.
- */
- public void retrieveOriginal(Uri contentUri, OriginalReady complete, ImageReady lowResolution) {
- File localFile = getLocalFile(contentUri);
- if (localFile != null) {
- addNotification(new NotifyOriginalReady(complete), localFile);
- } else {
- NotifyImageReady notifyLowResolution = (lowResolution == null) ? null
- : new NotifyImageReady(lowResolution);
- addTask(contentUri, new NotifyOriginalReady(complete), notifyLowResolution,
- MediaSize.Original);
- }
- }
-
- /**
- * Looks for an already cached media at a specific size.
- *
- * @param contentUri The original media item content URI
- * @param size The target size to search for in the cache
- * @return The cached file location or null if it is not cached.
- */
- public File getCachedFile(Uri contentUri, MediaSize size) {
- Long cachedId = mDatabaseHelper.getCached(contentUri, size);
- File file = null;
- if (cachedId != null) {
- file = createCacheImagePath(cachedId);
- if (!file.exists()) {
- mDatabaseHelper.delete(contentUri, size, mDeleteFile);
- file = null;
- }
- }
- return file;
- }
-
- /**
- * Inserts a media item into the cache.
- *
- * @param contentUri The original media item URI.
- * @param size The size of the media item to store in the cache.
- * @param tempFile The temporary file where the image is stored. This file
- * will no longer exist after executing this method.
- * @return The new location, in the cache, of the media item or null if it
- * wasn't possible to move into the cache.
- */
- public File insertIntoCache(Uri contentUri, MediaSize size, File tempFile) {
- long fileSize = tempFile.length();
- if (fileSize == 0) {
- return null;
- }
- File cacheFile = null;
- SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
- // Ensure that this step is atomic
- db.beginTransaction();
- try {
- Long id = mDatabaseHelper.getCached(contentUri, size);
- if (id != null) {
- cacheFile = createCacheImagePath(id);
- if (tempFile.renameTo(cacheFile)) {
- mDatabaseHelper.updateLength(id, fileSize);
- } else {
- Log.w(TAG, "Could not update cached file with " + tempFile);
- tempFile.delete();
- cacheFile = null;
- }
- } else {
- ensureFreeCacheSpace(tempFile.length(), size);
- id = mDatabaseHelper.insert(contentUri, size, mMoveTempToCache, tempFile);
- cacheFile = createCacheImagePath(id);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- return cacheFile;
- }
-
- /**
- * For testing purposes.
- */
- public void setMaxCacheSize(long maxCacheSize) {
- synchronized (mCacheSizeLock) {
- mMaxCacheSize = maxCacheSize;
- mMinThumbCacheSize = mMaxCacheSize / 10;
- mCacheSize = -1;
- mThumbCacheSize = -1;
- }
- }
-
- private File createCacheImagePath(long id) {
- return new File(getCacheDir(), String.valueOf(id) + IMAGE_EXTENSION);
- }
-
- private void addTask(Uri contentUri, ImageReady complete, ImageReady lowResolution,
- MediaSize size) {
- NotifyReady notifyComplete = new NotifyImageReady(complete);
- NotifyImageReady notifyLowResolution = null;
- if (lowResolution != null) {
- notifyLowResolution = new NotifyImageReady(lowResolution);
- }
- addTask(contentUri, notifyComplete, notifyLowResolution, size);
- }
-
- private void addTask(Uri contentUri, NotifyReady complete, NotifyImageReady lowResolution,
- MediaSize size) {
- MediaRetriever retriever = getMediaRetriever(contentUri);
- Uri uri = retriever.normalizeUri(contentUri, size);
- if (uri == null) {
- throw new IllegalArgumentException("No MediaRetriever for " + contentUri);
- }
- size = retriever.normalizeMediaSize(uri, size);
-
- File cachedFile = getCachedFile(uri, size);
- if (cachedFile != null) {
- addNotification(complete, cachedFile);
- return;
- }
- String differentiator = getDifferentiator(uri.getScheme(), uri.getAuthority());
- synchronized (mTasks) {
- List<ProcessingJob> tasks = mTasks.get(differentiator);
- if (tasks == null) {
- throw new IllegalArgumentException("Cannot find retriever for: " + uri);
- }
- synchronized (tasks) {
- ProcessingJob job = new ProcessingJob(uri, size, complete, lowResolution);
- if (complete.isPrefetch()) {
- tasks.add(job);
- } else {
- int index = tasks.size() - 1;
- while (index >= 0 && tasks.get(index).complete.isPrefetch()) {
- index--;
- }
- tasks.add(index + 1, job);
- }
- tasks.notifyAll();
- }
- }
- }
-
- private MediaRetriever getMediaRetriever(Uri uri) {
- String differentiator = getDifferentiator(uri.getScheme(), uri.getAuthority());
- MediaRetriever retriever;
- synchronized (mRetrievers) {
- retriever = mRetrievers.get(differentiator);
- }
- if (retriever == null) {
- throw new IllegalArgumentException("No MediaRetriever for " + uri);
- }
- return retriever;
- }
-
- private File getLocalFile(Uri uri) {
- MediaRetriever retriever = getMediaRetriever(uri);
- File localFile = null;
- if (retriever != null) {
- localFile = retriever.getLocalFile(uri);
- }
- return localFile;
- }
-
- private MediaSize getFastImageSize(Uri uri, MediaSize size) {
- MediaRetriever retriever = getMediaRetriever(uri);
- return retriever.getFastImageSize(uri, size);
- }
-
- private boolean isFastImageBetter(MediaSize fastImageType, MediaSize size) {
- if (fastImageType == null) {
- return false;
- }
- if (size == null) {
- return true;
- }
- return fastImageType.isBetterThan(size);
- }
-
- private byte[] getTemporaryImage(Uri uri, MediaSize fastImageType) {
- MediaRetriever retriever = getMediaRetriever(uri);
- return retriever.getTemporaryImage(uri, fastImageType);
- }
-
- private void processTask(ProcessingJob job) {
- File cachedFile = getCachedFile(job.contentUri, job.size);
- if (cachedFile != null) {
- addNotification(job.complete, cachedFile);
- return;
- }
-
- boolean hasLowResolution = job.lowResolution != null;
- if (hasLowResolution) {
- MediaSize cachedSize = mDatabaseHelper.executeOnBestCached(job.contentUri, job.size,
- mNotifyCachedLowResolution);
- MediaSize fastImageSize = getFastImageSize(job.contentUri, job.size);
- if (isFastImageBetter(fastImageSize, cachedSize)) {
- if (fastImageSize.isTemporary()) {
- byte[] bytes = getTemporaryImage(job.contentUri, fastImageSize);
- if (bytes != null) {
- addNotification(job.lowResolution, bytes);
- }
- } else {
- File lowFile = getMedia(job.contentUri, fastImageSize);
- if (lowFile != null) {
- addNotification(job.lowResolution, lowFile);
- }
- }
- }
- }
-
- // Now get the full size desired
- File fullSizeFile = getMedia(job.contentUri, job.size);
- if (fullSizeFile != null) {
- addNotification(job.complete, fullSizeFile);
- }
- }
-
- private void addNotification(NotifyReady callback, File file) {
- try {
- callback.setFile(file);
- synchronized (mCallbacks) {
- mCallbacks.add(callback);
- mCallbacks.notifyAll();
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Unable to read file " + file, e);
- }
- }
-
- private void addNotification(NotifyImageReady callback, byte[] bytes) {
- callback.setBytes(bytes);
- synchronized (mCallbacks) {
- mCallbacks.add(callback);
- mCallbacks.notifyAll();
- }
- }
-
- private File getMedia(Uri uri, MediaSize size) {
- long imageNumber;
- synchronized (mTempImageNumberLock) {
- imageNumber = mTempImageNumber++;
- }
- File tempFile = new File(getCacheDir(), String.valueOf(imageNumber) + TEMP_IMAGE_EXTENSION);
- MediaRetriever retriever = getMediaRetriever(uri);
- boolean retrieved = retriever.getMedia(uri, size, tempFile);
- File cachedFile = null;
- if (retrieved) {
- ensureFreeCacheSpace(tempFile.length(), size);
- long id = mDatabaseHelper.insert(uri, size, mMoveTempToCache, tempFile);
- cachedFile = createCacheImagePath(id);
- }
- return cachedFile;
- }
-
- private static String getDifferentiator(String scheme, String authority) {
- if (authority == null) {
- return scheme;
- }
- StringBuilder differentiator = new StringBuilder(scheme);
- differentiator.append(':');
- differentiator.append(authority);
- return differentiator.toString();
- }
-
- private void ensureFreeCacheSpace(long size, MediaSize mediaSize) {
- synchronized (mCacheSizeLock) {
- if (mCacheSize == -1 || mThumbCacheSize == -1) {
- mCacheSize = mDatabaseHelper.getCacheSize();
- mThumbCacheSize = mDatabaseHelper.getThumbnailCacheSize();
- if (mCacheSize == -1 || mThumbCacheSize == -1) {
- Log.e(TAG, "Can't determine size of the image cache");
- return;
- }
- }
- mCacheSize += size;
- if (mediaSize == MediaSize.Thumbnail) {
- mThumbCacheSize += size;
- }
- if (mCacheSize > mMaxCacheSize) {
- shrinkCacheLocked();
- }
- }
- }
-
- private void shrinkCacheLocked() {
- long deleteSize = mMinThumbCacheSize;
- boolean includeThumbnails = (mThumbCacheSize - deleteSize) > mMinThumbCacheSize;
- mDatabaseHelper.deleteOldCached(includeThumbnails, deleteSize, mDeleteFile);
- }
-}