From e58cb0d26c9fc8d1eb04d3cb86233b918053a47b Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 6 Aug 2013 13:39:38 -0700 Subject: Remove MediaCache and MediaCacheUtils. Change-Id: Iebf4b10ecde56bc490a1b5a4add31a1cf766e9c9 --- src/com/android/photos/data/FileRetriever.java | 109 ---- src/com/android/photos/data/MediaCache.java | 676 --------------------- .../android/photos/data/MediaCacheDatabase.java | 286 --------- src/com/android/photos/data/MediaCacheUtils.java | 167 ----- src/com/android/photos/data/MediaRetriever.java | 129 ---- 5 files changed, 1367 deletions(-) delete mode 100644 src/com/android/photos/data/FileRetriever.java delete mode 100644 src/com/android/photos/data/MediaCache.java delete mode 100644 src/com/android/photos/data/MediaCacheDatabase.java delete mode 100644 src/com/android/photos/data/MediaCacheUtils.java delete mode 100644 src/com/android/photos/data/MediaRetriever.java (limited to 'src/com/android/photos') diff --git a/src/com/android/photos/data/FileRetriever.java b/src/com/android/photos/data/FileRetriever.java deleted file mode 100644 index eb7686ef6..000000000 --- a/src/com/android/photos/data/FileRetriever.java +++ /dev/null @@ -1,109 +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.graphics.Bitmap; -import android.media.ExifInterface; -import android.net.Uri; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import com.android.gallery3d.common.BitmapUtils; - -import java.io.File; -import java.io.IOException; - -public class FileRetriever implements MediaRetriever { - private static final String TAG = FileRetriever.class.getSimpleName(); - - @Override - public File getLocalFile(Uri contentUri) { - return new File(contentUri.getPath()); - } - - @Override - public MediaSize getFastImageSize(Uri contentUri, MediaSize size) { - if (isVideo(contentUri)) { - return null; - } - return MediaSize.TemporaryThumbnail; - } - - @Override - public byte[] getTemporaryImage(Uri contentUri, MediaSize fastImageSize) { - - try { - ExifInterface exif = new ExifInterface(contentUri.getPath()); - if (exif.hasThumbnail()) { - return exif.getThumbnail(); - } - } catch (IOException e) { - Log.w(TAG, "Unable to load exif for " + contentUri); - } - return null; - } - - @Override - public boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile) { - if (imageSize == MediaSize.Original) { - return false; // getLocalFile should always return the original. - } - if (imageSize == MediaSize.Thumbnail) { - File preview = MediaCache.getInstance().getCachedFile(contentUri, MediaSize.Preview); - if (preview != null) { - // Just downsample the preview, it is faster. - return MediaCacheUtils.downsample(preview, imageSize, tempFile); - } - } - File highRes = new File(contentUri.getPath()); - boolean success; - if (!isVideo(contentUri)) { - success = MediaCacheUtils.downsample(highRes, imageSize, tempFile); - } else { - // Video needs to extract the bitmap. - Bitmap bitmap = BitmapUtils.createVideoThumbnail(highRes.getPath()); - if (bitmap == null) { - return false; - } else if (imageSize == MediaSize.Thumbnail - && !MediaCacheUtils.needsDownsample(bitmap, MediaSize.Preview) - && MediaCacheUtils.writeToFile(bitmap, tempFile)) { - // Opportunistically save preview - MediaCache mediaCache = MediaCache.getInstance(); - mediaCache.insertIntoCache(contentUri, MediaSize.Preview, tempFile); - } - // Now scale the image - success = MediaCacheUtils.downsample(bitmap, imageSize, tempFile); - } - return success; - } - - @Override - public Uri normalizeUri(Uri contentUri, MediaSize size) { - return contentUri; - } - - @Override - public MediaSize normalizeMediaSize(Uri contentUri, MediaSize size) { - return size; - } - - private static boolean isVideo(Uri uri) { - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); - String mimeType = mimeTypeMap.getMimeTypeFromExtension(extension); - return (mimeType != null && mimeType.startsWith("video/")); - } -} 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. - *

- * 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. - *

- *

- * Media items are uniquely identified by their content URIs. Each - * scheme/authority can offer its own MediaRetriever, running in its own thread. - *

- *

- * 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. - *

- */ -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 mQueue; - - public ProcessQueue(Queue 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 mCallbacks = new LinkedList(); - private Map mRetrievers = new HashMap(); - private Map> mTasks = new HashMap>(); - private List mProcessingThreads = new ArrayList(); - 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 queue = new LinkedList(); - 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 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); - } -} diff --git a/src/com/android/photos/data/MediaCacheDatabase.java b/src/com/android/photos/data/MediaCacheDatabase.java deleted file mode 100644 index c92ac0fdf..000000000 --- a/src/com/android/photos/data/MediaCacheDatabase.java +++ /dev/null @@ -1,286 +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.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; -import android.provider.BaseColumns; - -import com.android.photos.data.MediaRetriever.MediaSize; - -import java.io.File; - -class MediaCacheDatabase extends SQLiteOpenHelper { - public static final int DB_VERSION = 1; - public static final String DB_NAME = "mediacache.db"; - - /** Internal database table used for the media cache */ - public static final String TABLE = "media_cache"; - - private static interface Columns extends BaseColumns { - /** The Content URI of the original image. */ - public static final String URI = "uri"; - /** MediaSize.getValue() values. */ - public static final String MEDIA_SIZE = "media_size"; - /** The last time this image was queried. */ - public static final String LAST_ACCESS = "last_access"; - /** The image size in bytes. */ - public static final String SIZE_IN_BYTES = "size"; - } - - static interface Action { - void execute(Uri uri, long id, MediaSize size, Object parameter); - } - - private static final String[] PROJECTION_ID = { - Columns._ID, - }; - - private static final String[] PROJECTION_CACHED = { - Columns._ID, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, - }; - - private static final String[] PROJECTION_CACHE_SIZE = { - "SUM(" + Columns.SIZE_IN_BYTES + ")" - }; - - private static final String[] PROJECTION_DELETE_OLD = { - Columns._ID, Columns.URI, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, Columns.LAST_ACCESS, - }; - - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE + "(" - + Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + Columns.URI + " TEXT NOT NULL," - + Columns.MEDIA_SIZE + " INTEGER NOT NULL," - + Columns.LAST_ACCESS + " INTEGER NOT NULL," - + Columns.SIZE_IN_BYTES + " INTEGER NOT NULL," - + "UNIQUE(" + Columns.URI + ", " + Columns.MEDIA_SIZE + "))"; - - public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE; - - public static final String WHERE_THUMBNAIL = Columns.MEDIA_SIZE + " = " - + MediaSize.Thumbnail.getValue(); - - public static final String WHERE_NOT_THUMBNAIL = Columns.MEDIA_SIZE + " <> " - + MediaSize.Thumbnail.getValue(); - - public static final String WHERE_CLEAR_CACHE = Columns.LAST_ACCESS + " <= ?"; - - public static final String WHERE_CLEAR_CACHE_LARGE = WHERE_CLEAR_CACHE + " AND " - + WHERE_NOT_THUMBNAIL; - - static class QueryCacheResults { - public QueryCacheResults(long id, int sizeVal) { - this.id = id; - this.size = MediaSize.fromInteger(sizeVal); - } - public long id; - public MediaSize size; - } - - public MediaCacheDatabase(Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(CREATE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL(DROP_TABLE); - onCreate(db); - MediaCache.getInstance().clearCacheDir(); - } - - public Long getCached(Uri uri, MediaSize size) { - String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?"; - SQLiteDatabase db = getWritableDatabase(); - String[] whereArgs = { - uri.toString(), String.valueOf(size.getValue()), - }; - Cursor cursor = db.query(TABLE, PROJECTION_ID, where, whereArgs, null, null, null); - Long id = null; - if (cursor.moveToNext()) { - id = cursor.getLong(0); - } - cursor.close(); - if (id != null) { - String[] updateArgs = { - id.toString() - }; - ContentValues values = new ContentValues(); - values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); - db.beginTransaction(); - try { - db.update(TABLE, values, Columns._ID + " = ?", updateArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - return id; - } - - public MediaSize executeOnBestCached(Uri uri, MediaSize size, Action action) { - String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " < ?"; - String orderBy = Columns.MEDIA_SIZE + " DESC"; - SQLiteDatabase db = getReadableDatabase(); - String[] whereArgs = { - uri.toString(), String.valueOf(size.getValue()), - }; - Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, orderBy); - MediaSize bestSize = null; - if (cursor.moveToNext()) { - long id = cursor.getLong(0); - bestSize = MediaSize.fromInteger(cursor.getInt(1)); - long fileSize = cursor.getLong(2); - action.execute(uri, id, bestSize, fileSize); - } - cursor.close(); - return bestSize; - } - - public long insert(Uri uri, MediaSize size, Action action, File tempFile) { - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - ContentValues values = new ContentValues(); - values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); - values.put(Columns.MEDIA_SIZE, size.getValue()); - values.put(Columns.URI, uri.toString()); - values.put(Columns.SIZE_IN_BYTES, tempFile.length()); - long id = db.insert(TABLE, null, values); - if (id != -1) { - action.execute(uri, id, size, tempFile); - db.setTransactionSuccessful(); - } - return id; - } finally { - db.endTransaction(); - } - } - - public void updateLength(long id, long fileSize) { - ContentValues values = new ContentValues(); - values.put(Columns.SIZE_IN_BYTES, fileSize); - String[] whereArgs = { - String.valueOf(id) - }; - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - db.update(TABLE, values, Columns._ID + " = ?", whereArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public void delete(Uri uri, MediaSize size, Action action) { - String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?"; - String[] whereArgs = { - uri.toString(), String.valueOf(size.getValue()), - }; - deleteRows(uri, where, whereArgs, action); - } - - public void delete(Uri uri, Action action) { - String where = Columns.URI + " = ?"; - String[] whereArgs = { - uri.toString() - }; - deleteRows(uri, where, whereArgs, action); - } - - private void deleteRows(Uri uri, String where, String[] whereArgs, Action action) { - SQLiteDatabase db = getWritableDatabase(); - // Make this an atomic operation - db.beginTransaction(); - Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, null); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - MediaSize size = MediaSize.fromInteger(cursor.getInt(1)); - long length = cursor.getLong(2); - action.execute(uri, id, size, length); - } - cursor.close(); - try { - db.delete(TABLE, where, whereArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public void deleteOldCached(boolean includeThumbnails, long deleteSize, Action action) { - String where = includeThumbnails ? null : WHERE_NOT_THUMBNAIL; - long lastAccess = 0; - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - Cursor cursor = db.query(TABLE, PROJECTION_DELETE_OLD, where, null, null, null, - Columns.LAST_ACCESS); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - String uri = cursor.getString(1); - MediaSize size = MediaSize.fromInteger(cursor.getInt(2)); - long length = cursor.getLong(3); - long imageLastAccess = cursor.getLong(4); - - if (imageLastAccess != lastAccess && deleteSize < 0) { - break; // We've deleted enough. - } - lastAccess = imageLastAccess; - action.execute(Uri.parse(uri), id, size, length); - deleteSize -= length; - } - cursor.close(); - String[] whereArgs = { - String.valueOf(lastAccess), - }; - String whereDelete = includeThumbnails ? WHERE_CLEAR_CACHE : WHERE_CLEAR_CACHE_LARGE; - db.delete(TABLE, whereDelete, whereArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public long getCacheSize() { - return getCacheSize(null); - } - - public long getThumbnailCacheSize() { - return getCacheSize(WHERE_THUMBNAIL); - } - - private long getCacheSize(String where) { - SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.query(TABLE, PROJECTION_CACHE_SIZE, where, null, null, null, null); - long size = -1; - if (cursor.moveToNext()) { - size = cursor.getLong(0); - } - cursor.close(); - return size; - } -} diff --git a/src/com/android/photos/data/MediaCacheUtils.java b/src/com/android/photos/data/MediaCacheUtils.java deleted file mode 100644 index e3ccd1402..000000000 --- a/src/com/android/photos/data/MediaCacheUtils.java +++ /dev/null @@ -1,167 +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.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.BitmapFactory; -import android.util.Log; -import android.util.Pools.SimplePool; -import android.util.Pools.SynchronizedPool; - -import com.android.gallery3d.R; -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.data.DecodeUtils; -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.util.ThreadPool.CancelListener; -import com.android.gallery3d.util.ThreadPool.JobContext; -import com.android.photos.data.MediaRetriever.MediaSize; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class MediaCacheUtils { - private static final String TAG = MediaCacheUtils.class.getSimpleName(); - private static int QUALITY = 80; - private static final int BUFFER_SIZE = 4096; - private static final SimplePool mBufferPool = new SynchronizedPool(5); - - private static final JobContext sJobStub = new JobContext() { - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public void setCancelListener(CancelListener listener) { - } - - @Override - public boolean setMode(int mode) { - return true; - } - }; - - private static int mTargetThumbnailSize; - private static int mTargetPreviewSize; - - public static void initialize(Context context) { - Resources resources = context.getResources(); - mTargetThumbnailSize = resources.getDimensionPixelSize(R.dimen.size_thumbnail); - mTargetPreviewSize = resources.getDimensionPixelSize(R.dimen.size_preview); - } - - public static int getTargetSize(MediaSize size) { - return (size == MediaSize.Thumbnail) ? mTargetThumbnailSize : mTargetPreviewSize; - } - - public static boolean downsample(File inBitmap, MediaSize targetSize, File outBitmap) { - if (MediaSize.Original == targetSize) { - return false; // MediaCache should use the local path for this. - } - int size = getTargetSize(targetSize); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - // TODO: remove unnecessary job context from DecodeUtils. - Bitmap bitmap = DecodeUtils.decodeThumbnail(sJobStub, inBitmap.getPath(), options, size, - MediaItem.TYPE_THUMBNAIL); - boolean success = (bitmap != null); - if (success) { - success = writeAndRecycle(bitmap, outBitmap); - } - return success; - } - - public static boolean downsample(Bitmap inBitmap, MediaSize size, File outBitmap) { - if (MediaSize.Original == size) { - return false; // MediaCache should use the local path for this. - } - int targetSize = getTargetSize(size); - boolean success; - if (!needsDownsample(inBitmap, size)) { - success = writeAndRecycle(inBitmap, outBitmap); - } else { - float maxDimension = Math.max(inBitmap.getWidth(), inBitmap.getHeight()); - float scale = targetSize / maxDimension; - int targetWidth = Math.round(scale * inBitmap.getWidth()); - int targetHeight = Math.round(scale * inBitmap.getHeight()); - Bitmap scaled = Bitmap.createScaledBitmap(inBitmap, targetWidth, targetHeight, false); - success = writeAndRecycle(scaled, outBitmap); - inBitmap.recycle(); - } - return success; - } - - public static boolean extractImageFromVideo(File inVideo, File outBitmap) { - Bitmap bitmap = BitmapUtils.createVideoThumbnail(inVideo.getPath()); - return writeAndRecycle(bitmap, outBitmap); - } - - public static boolean needsDownsample(Bitmap bitmap, MediaSize size) { - if (size == MediaSize.Original) { - return false; - } - int targetSize = getTargetSize(size); - int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); - return maxDimension > (targetSize * 4 / 3); - } - - public static boolean writeAndRecycle(Bitmap bitmap, File outBitmap) { - boolean success = writeToFile(bitmap, outBitmap); - bitmap.recycle(); - return success; - } - - public static boolean writeToFile(Bitmap bitmap, File outBitmap) { - boolean success = false; - try { - FileOutputStream out = new FileOutputStream(outBitmap); - success = bitmap.compress(CompressFormat.JPEG, QUALITY, out); - out.close(); - } catch (IOException e) { - Log.w(TAG, "Couldn't write bitmap to cache", e); - // success is already false - } - return success; - } - - public static int copyStream(InputStream in, OutputStream out) throws IOException { - byte[] buffer = mBufferPool.acquire(); - if (buffer == null) { - buffer = new byte[BUFFER_SIZE]; - } - try { - int totalWritten = 0; - int bytesRead; - while ((bytesRead = in.read(buffer)) >= 0) { - out.write(buffer, 0, bytesRead); - totalWritten += bytesRead; - } - return totalWritten; - } finally { - Utils.closeSilently(in); - Utils.closeSilently(out); - mBufferPool.release(buffer); - } - } -} diff --git a/src/com/android/photos/data/MediaRetriever.java b/src/com/android/photos/data/MediaRetriever.java deleted file mode 100644 index f383e5ffa..000000000 --- a/src/com/android/photos/data/MediaRetriever.java +++ /dev/null @@ -1,129 +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.net.Uri; - -import java.io.File; - -public interface MediaRetriever { - public enum MediaSize { - TemporaryThumbnail(5), Thumbnail(10), TemporaryPreview(15), Preview(20), Original(30); - - private final int mValue; - - private MediaSize(int value) { - mValue = value; - } - - public int getValue() { - return mValue; - } - - static MediaSize fromInteger(int value) { - switch (value) { - case 10: - return MediaSize.Thumbnail; - case 20: - return MediaSize.Preview; - case 30: - return MediaSize.Original; - default: - throw new IllegalArgumentException(); - } - } - - public boolean isBetterThan(MediaSize that) { - return mValue > that.mValue; - } - - public boolean isTemporary() { - return this == TemporaryThumbnail || this == TemporaryPreview; - } - } - - /** - * Returns the local File for the given Uri. If the image is not stored - * locally, null should be returned. The image should not be retrieved if it - * isn't already available. - * - * @param contentUri The media URI to search for. - * @return The local File of the image if it is available or null if it - * isn't. - */ - File getLocalFile(Uri contentUri); - - /** - * Returns the fast access image type for a given image size, if supported. - * This image should be smaller than size and should be quick to retrieve. - * It does not have to obey the expected aspect ratio. - * - * @param contentUri The original media Uri. - * @param size The target size to search for a fast-access image. - * @return The fast image type supported for the given image size or null of - * no fast image is supported. - */ - MediaSize getFastImageSize(Uri contentUri, MediaSize size); - - /** - * Returns a byte array containing the contents of the fast temporary image - * for a given image size. For example, a thumbnail may be smaller or of a - * different aspect ratio than the generated thumbnail. - * - * @param contentUri The original media Uri. - * @param temporarySize The target media size. Guaranteed to be a MediaSize - * for which isTemporary() returns true. - * @return A byte array of contents for for the given contentUri and - * fastImageType. null can be retrieved if the quick retrieval - * fails. - */ - byte[] getTemporaryImage(Uri contentUri, MediaSize temporarySize); - - /** - * Retrieves an image and saves it to a file. - * - * @param contentUri The original media Uri. - * @param size The target media size. - * @param tempFile The file to write the bitmap to. - * @return true on success. - */ - boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile); - - /** - * Normalizes a URI that may have additional parameters. It is fine to - * return contentUri. This is executed on the calling thread, so it must be - * a fast access operation and cannot depend, for example, on I/O. - * - * @param contentUri The URI to normalize - * @param size The size of the image being requested - * @return The normalized URI representation of contentUri. - */ - Uri normalizeUri(Uri contentUri, MediaSize size); - - /** - * Normalize the MediaSize for a given URI. Typically the size returned - * would be the passed-in size. Some URIs may only have one size used and - * should be treaded as Thumbnails, for example. This is executed on the - * calling thread, so it must be a fast access operation and cannot depend, - * for example, on I/O. - * - * @param contentUri The URI for the size being normalized. - * @param size The size to be normalized. - * @return The normalized size of the given URI. - */ - MediaSize normalizeMediaSize(Uri contentUri, MediaSize size); -} -- cgit v1.2.3