diff options
Diffstat (limited to 'src/com/android/photos/data')
-rw-r--r-- | src/com/android/photos/data/AlbumSetLoader.java | 54 | ||||
-rw-r--r-- | src/com/android/photos/data/BitmapDecoder.java | 224 | ||||
-rw-r--r-- | src/com/android/photos/data/FileRetriever.java | 109 | ||||
-rw-r--r-- | src/com/android/photos/data/GalleryBitmapPool.java | 161 | ||||
-rw-r--r-- | src/com/android/photos/data/MediaCache.java | 676 | ||||
-rw-r--r-- | src/com/android/photos/data/MediaCacheDatabase.java | 286 | ||||
-rw-r--r-- | src/com/android/photos/data/MediaCacheUtils.java | 167 | ||||
-rw-r--r-- | src/com/android/photos/data/MediaRetriever.java | 129 | ||||
-rw-r--r-- | src/com/android/photos/data/NotificationWatcher.java | 55 | ||||
-rw-r--r-- | src/com/android/photos/data/PhotoDatabase.java | 195 | ||||
-rw-r--r-- | src/com/android/photos/data/PhotoProvider.java | 536 | ||||
-rw-r--r-- | src/com/android/photos/data/PhotoSetLoader.java | 115 | ||||
-rw-r--r-- | src/com/android/photos/data/SQLiteContentProvider.java | 265 | ||||
-rw-r--r-- | src/com/android/photos/data/SparseArrayBitmapPool.java | 212 |
14 files changed, 0 insertions, 3184 deletions
diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java deleted file mode 100644 index 940473255..000000000 --- a/src/com/android/photos/data/AlbumSetLoader.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.android.photos.data; - -import android.database.MatrixCursor; - - -public class AlbumSetLoader { - public static final int INDEX_ID = 0; - public static final int INDEX_TITLE = 1; - public static final int INDEX_TIMESTAMP = 2; - public static final int INDEX_THUMBNAIL_URI = 3; - public static final int INDEX_THUMBNAIL_WIDTH = 4; - public static final int INDEX_THUMBNAIL_HEIGHT = 5; - public static final int INDEX_COUNT_PENDING_UPLOAD = 6; - public static final int INDEX_COUNT = 7; - public static final int INDEX_SUPPORTED_OPERATIONS = 8; - - public static final String[] PROJECTION = { - "_id", - "title", - "timestamp", - "thumb_uri", - "thumb_width", - "thumb_height", - "count_pending_upload", - "_count", - "supported_operations" - }; - public static final MatrixCursor MOCK = createRandomCursor(30); - - private static MatrixCursor createRandomCursor(int count) { - MatrixCursor c = new MatrixCursor(PROJECTION, count); - for (int i = 0; i < count; i++) { - c.addRow(createRandomRow()); - } - return c; - } - - private static Object[] createRandomRow() { - double random = Math.random(); - int id = (int) (500 * random); - Object[] row = { - id, - "Fun times " + id, - (long) (System.currentTimeMillis() * random), - null, - 0, - 0, - (random < .3 ? 1 : 0), - 1, - 0 - }; - return row; - } -}
\ No newline at end of file diff --git a/src/com/android/photos/data/BitmapDecoder.java b/src/com/android/photos/data/BitmapDecoder.java deleted file mode 100644 index 0671e73ca..000000000 --- a/src/com/android/photos/data/BitmapDecoder.java +++ /dev/null @@ -1,224 +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.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; -import android.util.Log; -import android.util.Pools.Pool; -import android.util.Pools.SynchronizedPool; - -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -/** - * BitmapDecoder keeps a pool of temporary storage to reuse for decoding - * bitmaps. It also simplifies the multi-stage decoding required to efficiently - * use GalleryBitmapPool. The static methods decode and decodeFile can be used - * to decode a bitmap from GalleryBitmapPool. The bitmap may be returned - * directly to GalleryBitmapPool or use the put method here when the bitmap is - * ready to be recycled. - */ -public class BitmapDecoder { - private static final String TAG = BitmapDecoder.class.getSimpleName(); - private static final int POOL_SIZE = 4; - private static final int TEMP_STORAGE_SIZE_BYTES = 16 * 1024; - private static final int HEADER_MAX_SIZE = 128 * 1024; - private static final int NO_SCALING = -1; - - private static final Pool<BitmapFactory.Options> sOptions = - new SynchronizedPool<BitmapFactory.Options>(POOL_SIZE); - - private interface Decoder<T> { - Bitmap decode(T input, BitmapFactory.Options options); - - boolean decodeBounds(T input, BitmapFactory.Options options); - } - - private static abstract class OnlyDecode<T> implements Decoder<T> { - @Override - public boolean decodeBounds(T input, BitmapFactory.Options options) { - decode(input, options); - return true; - } - } - - private static final Decoder<InputStream> sStreamDecoder = new Decoder<InputStream>() { - @Override - public Bitmap decode(InputStream is, Options options) { - return BitmapFactory.decodeStream(is, null, options); - } - - @Override - public boolean decodeBounds(InputStream is, Options options) { - is.mark(HEADER_MAX_SIZE); - BitmapFactory.decodeStream(is, null, options); - try { - is.reset(); - return true; - } catch (IOException e) { - Log.e(TAG, "Could not decode stream to bitmap", e); - return false; - } - } - }; - - private static final Decoder<String> sFileDecoder = new OnlyDecode<String>() { - @Override - public Bitmap decode(String filePath, Options options) { - return BitmapFactory.decodeFile(filePath, options); - } - }; - - private static final Decoder<byte[]> sByteArrayDecoder = new OnlyDecode<byte[]>() { - @Override - public Bitmap decode(byte[] data, Options options) { - return BitmapFactory.decodeByteArray(data, 0, data.length, options); - } - }; - - private static <T> Bitmap delegateDecode(Decoder<T> decoder, T input, int width, int height) { - BitmapFactory.Options options = getOptions(); - GalleryBitmapPool pool = GalleryBitmapPool.getInstance(); - try { - options.inJustDecodeBounds = true; - if (!decoder.decodeBounds(input, options)) { - return null; - } - options.inJustDecodeBounds = false; - Bitmap reuseBitmap = null; - if (width != NO_SCALING && options.outWidth >= width && options.outHeight >= height) { - setScaling(options, width, height); - } else { - reuseBitmap = pool.get(options.outWidth, options.outHeight); - } - options.inBitmap = reuseBitmap; - Bitmap decodedBitmap = decoder.decode(input, options); - if (reuseBitmap != null && decodedBitmap != reuseBitmap) { - pool.put(reuseBitmap); - } - return decodedBitmap; - } catch (IllegalArgumentException e) { - if (options.inBitmap == null) { - throw e; - } - pool.put(options.inBitmap); - options.inBitmap = null; - return decoder.decode(input, options); - } finally { - options.inBitmap = null; - options.inJustDecodeBounds = false; - sOptions.release(options); - } - } - - public static Bitmap decode(InputStream in) { - try { - if (!in.markSupported()) { - in = new BufferedInputStream(in); - } - return delegateDecode(sStreamDecoder, in, NO_SCALING, NO_SCALING); - } finally { - Utils.closeSilently(in); - } - } - - public static Bitmap decode(File file) { - return decodeFile(file.getPath()); - } - - public static Bitmap decodeFile(String path) { - return delegateDecode(sFileDecoder, path, NO_SCALING, NO_SCALING); - } - - public static Bitmap decodeByteArray(byte[] data) { - return delegateDecode(sByteArrayDecoder, data, NO_SCALING, NO_SCALING); - } - - public static void put(Bitmap bitmap) { - GalleryBitmapPool.getInstance().put(bitmap); - } - - /** - * Decodes to a specific size. If the dimensions of the image don't match - * width x height, the resulting image will be in the proportions of the - * decoded image, but will be scaled to fill the dimensions. For example, if - * width and height are 10x10 and the image is 200x100, the resulting image - * will be scaled/sampled to 20x10. - */ - public static Bitmap decodeFile(String path, int width, int height) { - return delegateDecode(sFileDecoder, path, width, height); - } - - /** @see #decodeFile(String, int, int) */ - public static Bitmap decodeByteArray(byte[] data, int width, int height) { - return delegateDecode(sByteArrayDecoder, data, width, height); - } - - /** @see #decodeFile(String, int, int) */ - public static Bitmap decode(InputStream in, int width, int height) { - try { - if (!in.markSupported()) { - in = new BufferedInputStream(in); - } - return delegateDecode(sStreamDecoder, in, width, height); - } finally { - Utils.closeSilently(in); - } - } - - private static BitmapFactory.Options getOptions() { - BitmapFactory.Options opts = sOptions.acquire(); - if (opts == null) { - opts = new BitmapFactory.Options(); - opts.inMutable = true; - opts.inPreferredConfig = Config.ARGB_8888; - opts.inTempStorage = new byte[TEMP_STORAGE_SIZE_BYTES]; - } - opts.inSampleSize = 1; - opts.inDensity = 1; - opts.inTargetDensity = 1; - - return opts; - } - - // Sets the options to sample then scale the image so that the image's - // minimum dimension will match side. - private static void setScaling(BitmapFactory.Options options, int width, int height) { - float widthScale = ((float)options.outWidth)/ width; - float heightScale = ((float) options.outHeight)/height; - int side = (widthScale < heightScale) ? width : height; - options.inSampleSize = BitmapUtils.computeSampleSize(options.outWidth, options.outHeight, - side, BitmapUtils.UNCONSTRAINED); - int constraint; - if (options.outWidth < options.outHeight) { - // Width is the constraint. Scale so that width = side. - constraint = options.outWidth; - } else { - // Height is the constraint. Scale so that height = side. - constraint = options.outHeight; - } - options.inDensity = constraint / options.inSampleSize; - options.inTargetDensity = side; - } -} 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/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java deleted file mode 100644 index 390a0d42f..000000000 --- a/src/com/android/photos/data/GalleryBitmapPool.java +++ /dev/null @@ -1,161 +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.graphics.Point; -import android.util.Pools.Pool; -import android.util.Pools.SynchronizedPool; - -import com.android.photos.data.SparseArrayBitmapPool.Node; - -/** - * Pool allowing the efficient reuse of bitmaps in order to avoid long - * garbage collection pauses. - */ -public class GalleryBitmapPool { - - private static final int CAPACITY_BYTES = 20971520; - - // We found that Gallery uses bitmaps that are either square (for example, - // tiles of large images or square thumbnails), match one of the common - // photo aspect ratios (4x3, 3x2, or 16x9), or, less commonly, are of some - // other aspect ratio. Taking advantage of this information, we use 3 - // SparseArrayBitmapPool instances to back the GalleryBitmapPool, which affords - // O(1) lookups for square bitmaps, and average-case - but *not* asymptotically - - // O(1) lookups for common photo aspect ratios and other miscellaneous aspect - // ratios. Beware of the pathological case where there are many bitmaps added - // to the pool with different non-square aspect ratios but the same width, as - // performance will degrade and the average case lookup will approach - // O(# of different aspect ratios). - private static final int POOL_INDEX_NONE = -1; - private static final int POOL_INDEX_SQUARE = 0; - private static final int POOL_INDEX_PHOTO = 1; - private static final int POOL_INDEX_MISC = 2; - - private static final Point[] COMMON_PHOTO_ASPECT_RATIOS = - { new Point(4, 3), new Point(3, 2), new Point(16, 9) }; - - private int mCapacityBytes; - private SparseArrayBitmapPool [] mPools; - private Pool<Node> mSharedNodePool = new SynchronizedPool<Node>(128); - - private GalleryBitmapPool(int capacityBytes) { - mPools = new SparseArrayBitmapPool[3]; - mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); - mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); - mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); - mCapacityBytes = capacityBytes; - } - - private static GalleryBitmapPool sInstance = new GalleryBitmapPool(CAPACITY_BYTES); - - public static GalleryBitmapPool getInstance() { - return sInstance; - } - - private SparseArrayBitmapPool getPoolForDimensions(int width, int height) { - int index = getPoolIndexForDimensions(width, height); - if (index == POOL_INDEX_NONE) { - return null; - } else { - return mPools[index]; - } - } - - private int getPoolIndexForDimensions(int width, int height) { - if (width <= 0 || height <= 0) { - return POOL_INDEX_NONE; - } - if (width == height) { - return POOL_INDEX_SQUARE; - } - int min, max; - if (width > height) { - min = height; - max = width; - } else { - min = width; - max = height; - } - for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) { - if (min * ar.x == max * ar.y) { - return POOL_INDEX_PHOTO; - } - } - return POOL_INDEX_MISC; - } - - /** - * @return Capacity of the pool in bytes. - */ - public synchronized int getCapacity() { - return mCapacityBytes; - } - - /** - * @return Approximate total size in bytes of the bitmaps stored in the pool. - */ - public int getSize() { - // Note that this only returns an approximate size, since multiple threads - // might be getting and putting Bitmaps from the pool and we lock at the - // sub-pool level to avoid unnecessary blocking. - int total = 0; - for (SparseArrayBitmapPool p : mPools) { - total += p.getSize(); - } - return total; - } - - /** - * @return Bitmap from the pool with the desired height/width or null if none available. - */ - public Bitmap get(int width, int height) { - SparseArrayBitmapPool pool = getPoolForDimensions(width, height); - if (pool == null) { - return null; - } else { - return pool.get(width, height); - } - } - - /** - * Adds the given bitmap to the pool. - * @return Whether the bitmap was added to the pool. - */ - public boolean put(Bitmap b) { - if (b == null || b.getConfig() != Bitmap.Config.ARGB_8888) { - return false; - } - SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight()); - if (pool == null) { - b.recycle(); - return false; - } else { - return pool.put(b); - } - } - - /** - * Empty the pool, recycling all the bitmaps currently in it. - */ - public void clear() { - for (SparseArrayBitmapPool p : mPools) { - p.clear(); - } - } -} 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); - } -} 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<byte[]> mBufferPool = new SynchronizedPool<byte[]>(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 <code>true</code> 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); -} diff --git a/src/com/android/photos/data/NotificationWatcher.java b/src/com/android/photos/data/NotificationWatcher.java deleted file mode 100644 index 9041c236f..000000000 --- a/src/com/android/photos/data/NotificationWatcher.java +++ /dev/null @@ -1,55 +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 com.android.photos.data.PhotoProvider.ChangeNotification; - -import java.util.ArrayList; - -/** - * Used for capturing notifications from PhotoProvider without relying on - * ContentResolver. MockContentResolver does not allow sending notification to - * ContentObservers, so PhotoProvider allows this alternative for testing. - */ -public class NotificationWatcher implements ChangeNotification { - private ArrayList<Uri> mUris = new ArrayList<Uri>(); - private boolean mSyncToNetwork = false; - - @Override - public void notifyChange(Uri uri, boolean syncToNetwork) { - mUris.add(uri); - mSyncToNetwork = mSyncToNetwork || syncToNetwork; - } - - public boolean isNotified(Uri uri) { - return mUris.contains(uri); - } - - public int notificationCount() { - return mUris.size(); - } - - public boolean syncToNetwork() { - return mSyncToNetwork; - } - - public void reset() { - mUris.clear(); - mSyncToNetwork = false; - } -} diff --git a/src/com/android/photos/data/PhotoDatabase.java b/src/com/android/photos/data/PhotoDatabase.java deleted file mode 100644 index 0c7b22730..000000000 --- a/src/com/android/photos/data/PhotoDatabase.java +++ /dev/null @@ -1,195 +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.database.sqlite.SQLiteOpenHelper; - -import com.android.photos.data.PhotoProvider.Accounts; -import com.android.photos.data.PhotoProvider.Albums; -import com.android.photos.data.PhotoProvider.Metadata; -import com.android.photos.data.PhotoProvider.Photos; - -import java.util.ArrayList; -import java.util.List; - -/** - * Used in PhotoProvider to create and access the database containing - * information about photo and video information stored on the server. - */ -public class PhotoDatabase extends SQLiteOpenHelper { - @SuppressWarnings("unused") - private static final String TAG = PhotoDatabase.class.getSimpleName(); - static final int DB_VERSION = 3; - - private static final String SQL_CREATE_TABLE = "CREATE TABLE "; - - private static final String[][] CREATE_PHOTO = { - { Photos._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - // Photos.ACCOUNT_ID is a foreign key to Accounts._ID - { Photos.ACCOUNT_ID, "INTEGER NOT NULL" }, - { Photos.WIDTH, "INTEGER NOT NULL" }, - { Photos.HEIGHT, "INTEGER NOT NULL" }, - { Photos.DATE_TAKEN, "INTEGER NOT NULL" }, - // Photos.ALBUM_ID is a foreign key to Albums._ID - { Photos.ALBUM_ID, "INTEGER" }, - { Photos.MIME_TYPE, "TEXT NOT NULL" }, - { Photos.TITLE, "TEXT" }, - { Photos.DATE_MODIFIED, "INTEGER" }, - { Photos.ROTATION, "INTEGER" }, - }; - - private static final String[][] CREATE_ALBUM = { - { Albums._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - // Albums.ACCOUNT_ID is a foreign key to Accounts._ID - { Albums.ACCOUNT_ID, "INTEGER NOT NULL" }, - // Albums.PARENT_ID is a foreign key to Albums._ID - { Albums.PARENT_ID, "INTEGER" }, - { Albums.ALBUM_TYPE, "TEXT" }, - { Albums.VISIBILITY, "INTEGER NOT NULL" }, - { Albums.LOCATION_STRING, "TEXT" }, - { Albums.TITLE, "TEXT NOT NULL" }, - { Albums.SUMMARY, "TEXT" }, - { Albums.DATE_PUBLISHED, "INTEGER" }, - { Albums.DATE_MODIFIED, "INTEGER" }, - createUniqueConstraint(Albums.PARENT_ID, Albums.TITLE), - }; - - private static final String[][] CREATE_METADATA = { - { Metadata._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - // Metadata.PHOTO_ID is a foreign key to Photos._ID - { Metadata.PHOTO_ID, "INTEGER NOT NULL" }, - { Metadata.KEY, "TEXT NOT NULL" }, - { Metadata.VALUE, "TEXT NOT NULL" }, - createUniqueConstraint(Metadata.PHOTO_ID, Metadata.KEY), - }; - - private static final String[][] CREATE_ACCOUNT = { - { Accounts._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - { Accounts.ACCOUNT_NAME, "TEXT UNIQUE NOT NULL" }, - }; - - @Override - public void onCreate(SQLiteDatabase db) { - createTable(db, Accounts.TABLE, getAccountTableDefinition()); - createTable(db, Albums.TABLE, getAlbumTableDefinition()); - createTable(db, Photos.TABLE, getPhotoTableDefinition()); - createTable(db, Metadata.TABLE, getMetadataTableDefinition()); - } - - public PhotoDatabase(Context context, String dbName, int dbVersion) { - super(context, dbName, null, dbVersion); - } - - public PhotoDatabase(Context context, String dbName) { - super(context, dbName, null, DB_VERSION); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - recreate(db); - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - recreate(db); - } - - private void recreate(SQLiteDatabase db) { - dropTable(db, Metadata.TABLE); - dropTable(db, Photos.TABLE); - dropTable(db, Albums.TABLE); - dropTable(db, Accounts.TABLE); - onCreate(db); - } - - protected List<String[]> getAlbumTableDefinition() { - return tableCreationStrings(CREATE_ALBUM); - } - - protected List<String[]> getPhotoTableDefinition() { - return tableCreationStrings(CREATE_PHOTO); - } - - protected List<String[]> getMetadataTableDefinition() { - return tableCreationStrings(CREATE_METADATA); - } - - protected List<String[]> getAccountTableDefinition() { - return tableCreationStrings(CREATE_ACCOUNT); - } - - protected static void createTable(SQLiteDatabase db, String table, List<String[]> columns) { - StringBuilder create = new StringBuilder(SQL_CREATE_TABLE); - create.append(table).append('('); - boolean first = true; - for (String[] column : columns) { - if (!first) { - create.append(','); - } - first = false; - for (String val: column) { - create.append(val).append(' '); - } - } - create.append(')'); - db.beginTransaction(); - try { - db.execSQL(create.toString()); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - protected static String[] createUniqueConstraint(String column1, String column2) { - return new String[] { - "UNIQUE(", column1, ",", column2, ")" - }; - } - - protected static List<String[]> tableCreationStrings(String[][] createTable) { - ArrayList<String[]> create = new ArrayList<String[]>(createTable.length); - for (String[] line: createTable) { - create.add(line); - } - return create; - } - - protected static void addToTable(List<String[]> createTable, String[][] columns, String[][] constraints) { - if (columns != null) { - for (String[] column: columns) { - createTable.add(0, column); - } - } - if (constraints != null) { - for (String[] constraint: constraints) { - createTable.add(constraint); - } - } - } - - protected static void dropTable(SQLiteDatabase db, String table) { - db.beginTransaction(); - try { - db.execSQL("drop table if exists " + table); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } -} diff --git a/src/com/android/photos/data/PhotoProvider.java b/src/com/android/photos/data/PhotoProvider.java deleted file mode 100644 index d4310ca95..000000000 --- a/src/com/android/photos/data/PhotoProvider.java +++ /dev/null @@ -1,536 +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.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.media.ExifInterface; -import android.net.Uri; -import android.os.CancellationSignal; -import android.provider.BaseColumns; - -import com.android.gallery3d.common.ApiHelper; - -import java.util.List; - -/** - * A provider that gives access to photo and video information for media stored - * on the server. Only media that is or will be put on the server will be - * accessed by this provider. Use Photos.CONTENT_URI to query all photos and - * videos. Use Albums.CONTENT_URI to query all albums. Use Metadata.CONTENT_URI - * to query metadata about a photo or video, based on the ID of the media. Use - * ImageCache.THUMBNAIL_CONTENT_URI, ImageCache.PREVIEW_CONTENT_URI, or - * ImageCache.ORIGINAL_CONTENT_URI to query the path of the thumbnail, preview, - * or original-sized image respectfully. <br/> - * To add or update metadata, use the update function rather than insert. All - * values for the metadata must be in the ContentValues, even if they are also - * in the selection. The selection and selectionArgs are not used when updating - * metadata. If the metadata values are null, the row will be deleted. - */ -public class PhotoProvider extends SQLiteContentProvider { - @SuppressWarnings("unused") - private static final String TAG = PhotoProvider.class.getSimpleName(); - - protected static final String DB_NAME = "photo.db"; - public static final String AUTHORITY = PhotoProviderAuthority.AUTHORITY; - static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme("content").authority(AUTHORITY) - .build(); - - // Used to allow mocking out the change notification because - // MockContextResolver disallows system-wide notification. - public static interface ChangeNotification { - void notifyChange(Uri uri, boolean syncToNetwork); - } - - /** - * Contains columns that can be accessed via Accounts.CONTENT_URI - */ - public static interface Accounts extends BaseColumns { - /** - * Internal database table used for account information - */ - public static final String TABLE = "accounts"; - /** - * Content URI for account information - */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - /** - * User name for this account. - */ - public static final String ACCOUNT_NAME = "name"; - } - - /** - * Contains columns that can be accessed via Photos.CONTENT_URI. - */ - public static interface Photos extends BaseColumns { - /** - * The image_type query parameter required for requesting a specific - * size of image. - */ - public static final String MEDIA_SIZE_QUERY_PARAMETER = "media_size"; - - /** Internal database table used for basic photo information. */ - public static final String TABLE = "photos"; - /** Content URI for basic photo and video information. */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - - /** Long foreign key to Accounts._ID */ - public static final String ACCOUNT_ID = "account_id"; - /** Column name for the width of the original image. Integer value. */ - public static final String WIDTH = "width"; - /** Column name for the height of the original image. Integer value. */ - public static final String HEIGHT = "height"; - /** - * Column name for the date that the original image was taken. Long - * value indicating the milliseconds since epoch in the GMT time zone. - */ - public static final String DATE_TAKEN = "date_taken"; - /** - * Column name indicating the long value of the album id that this image - * resides in. Will be NULL if it it has not been uploaded to the - * server. - */ - public static final String ALBUM_ID = "album_id"; - /** The column name for the mime-type String. */ - public static final String MIME_TYPE = "mime_type"; - /** The title of the photo. String value. */ - public static final String TITLE = "title"; - /** The date the photo entry was last updated. Long value. */ - public static final String DATE_MODIFIED = "date_modified"; - /** - * The rotation of the photo in degrees, if rotation has not already - * been applied. Integer value. - */ - public static final String ROTATION = "rotation"; - } - - /** - * Contains columns and Uri for accessing album information. - */ - public static interface Albums extends BaseColumns { - /** Internal database table used album information. */ - public static final String TABLE = "albums"; - /** Content URI for album information. */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - - /** Long foreign key to Accounts._ID */ - public static final String ACCOUNT_ID = "account_id"; - /** Parent directory or null if this is in the root. */ - public static final String PARENT_ID = "parent_id"; - /** The type of album. Non-null, if album is auto-generated. String value. */ - public static final String ALBUM_TYPE = "album_type"; - /** - * Column name for the visibility level of the album. Can be any of the - * VISIBILITY_* values. - */ - public static final String VISIBILITY = "visibility"; - /** The user-specified location associated with the album. String value. */ - public static final String LOCATION_STRING = "location_string"; - /** The title of the album. String value. */ - public static final String TITLE = "title"; - /** A short summary of the contents of the album. String value. */ - public static final String SUMMARY = "summary"; - /** The date the album was created. Long value */ - public static final String DATE_PUBLISHED = "date_published"; - /** The date the album entry was last updated. Long value. */ - public static final String DATE_MODIFIED = "date_modified"; - - // Privacy values for Albums.VISIBILITY - public static final int VISIBILITY_PRIVATE = 1; - public static final int VISIBILITY_SHARED = 2; - public static final int VISIBILITY_PUBLIC = 3; - } - - /** - * Contains columns and Uri for accessing photo and video metadata - */ - public static interface Metadata extends BaseColumns { - /** Internal database table used metadata information. */ - public static final String TABLE = "metadata"; - /** Content URI for photo and video metadata. */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - /** Foreign key to photo_id. Long value. */ - public static final String PHOTO_ID = "photo_id"; - /** Metadata key. String value */ - public static final String KEY = "key"; - /** - * Metadata value. Type is based on key. - */ - public static final String VALUE = "value"; - - /** A short summary of the photo. String value. */ - public static final String KEY_SUMMARY = "summary"; - /** The date the photo was added. Long value. */ - public static final String KEY_PUBLISHED = "date_published"; - /** The date the photo was last updated. Long value. */ - public static final String KEY_DATE_UPDATED = "date_updated"; - /** The size of the photo is bytes. Integer value. */ - public static final String KEY_SIZE_IN_BTYES = "size"; - /** The latitude associated with the photo. Double value. */ - public static final String KEY_LATITUDE = "latitude"; - /** The longitude associated with the photo. Double value. */ - public static final String KEY_LONGITUDE = "longitude"; - - /** The make of the camera used. String value. */ - public static final String KEY_EXIF_MAKE = ExifInterface.TAG_MAKE; - /** The model of the camera used. String value. */ - public static final String KEY_EXIF_MODEL = ExifInterface.TAG_MODEL;; - /** The exposure time used. Float value. */ - public static final String KEY_EXIF_EXPOSURE = ExifInterface.TAG_EXPOSURE_TIME; - /** Whether the flash was used. Boolean value. */ - public static final String KEY_EXIF_FLASH = ExifInterface.TAG_FLASH; - /** The focal length used. Float value. */ - public static final String KEY_EXIF_FOCAL_LENGTH = ExifInterface.TAG_FOCAL_LENGTH; - /** The fstop value used. Float value. */ - public static final String KEY_EXIF_FSTOP = ExifInterface.TAG_APERTURE; - /** The ISO equivalent value used. Integer value. */ - public static final String KEY_EXIF_ISO = ExifInterface.TAG_ISO; - } - - // SQL used within this class. - protected static final String WHERE_ID = BaseColumns._ID + " = ?"; - protected static final String WHERE_METADATA_ID = Metadata.PHOTO_ID + " = ? AND " - + Metadata.KEY + " = ?"; - - protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM " - + Albums.TABLE; - protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM " - + Photos.TABLE; - protected static final String SELECT_PHOTO_COUNT = "SELECT COUNT(*) FROM " + Photos.TABLE; - protected static final String DELETE_PHOTOS = "DELETE FROM " + Photos.TABLE; - protected static final String DELETE_METADATA = "DELETE FROM " + Metadata.TABLE; - protected static final String SELECT_METADATA_COUNT = "SELECT COUNT(*) FROM " + Metadata.TABLE; - protected static final String WHERE = " WHERE "; - protected static final String IN = " IN "; - protected static final String NESTED_SELECT_START = "("; - protected static final String NESTED_SELECT_END = ")"; - protected static final String[] PROJECTION_COUNT = { - "COUNT(*)" - }; - - /** - * For selecting the mime-type for an image. - */ - private static final String[] PROJECTION_MIME_TYPE = { - Photos.MIME_TYPE, - }; - - protected static final String[] BASE_COLUMNS_ID = { - BaseColumns._ID, - }; - - protected ChangeNotification mNotifier = null; - protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - protected static final int MATCH_PHOTO = 1; - protected static final int MATCH_PHOTO_ID = 2; - protected static final int MATCH_ALBUM = 3; - protected static final int MATCH_ALBUM_ID = 4; - protected static final int MATCH_METADATA = 5; - protected static final int MATCH_METADATA_ID = 6; - protected static final int MATCH_ACCOUNT = 7; - protected static final int MATCH_ACCOUNT_ID = 8; - - static { - sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO); - // match against Photos._ID - sUriMatcher.addURI(AUTHORITY, Photos.TABLE + "/#", MATCH_PHOTO_ID); - sUriMatcher.addURI(AUTHORITY, Albums.TABLE, MATCH_ALBUM); - // match against Albums._ID - sUriMatcher.addURI(AUTHORITY, Albums.TABLE + "/#", MATCH_ALBUM_ID); - sUriMatcher.addURI(AUTHORITY, Metadata.TABLE, MATCH_METADATA); - // match against metadata/<Metadata._ID> - sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID); - sUriMatcher.addURI(AUTHORITY, Accounts.TABLE, MATCH_ACCOUNT); - // match against Accounts._ID - sUriMatcher.addURI(AUTHORITY, Accounts.TABLE + "/#", MATCH_ACCOUNT_ID); - } - - @Override - public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, - boolean callerIsSyncAdapter) { - int match = matchUri(uri); - selection = addIdToSelection(match, selection); - selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs); - return deleteCascade(uri, match, selection, selectionArgs); - } - - @Override - public String getType(Uri uri) { - Cursor cursor = query(uri, PROJECTION_MIME_TYPE, null, null, null); - String mimeType = null; - if (cursor.moveToNext()) { - mimeType = cursor.getString(0); - } - cursor.close(); - return mimeType; - } - - @Override - public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) { - int match = matchUri(uri); - validateMatchTable(match); - String table = getTableFromMatch(match, uri); - SQLiteDatabase db = getDatabaseHelper().getWritableDatabase(); - Uri insertedUri = null; - long id = db.insert(table, null, values); - if (id != -1) { - // uri already matches the table. - insertedUri = ContentUris.withAppendedId(uri, id); - postNotifyUri(insertedUri); - } - return insertedUri; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return query(uri, projection, selection, selectionArgs, sortOrder, null); - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder, CancellationSignal cancellationSignal) { - projection = replaceCount(projection); - int match = matchUri(uri); - selection = addIdToSelection(match, selection); - selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs); - String table = getTableFromMatch(match, uri); - Cursor c = query(table, projection, selection, selectionArgs, sortOrder, cancellationSignal); - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), uri); - } - return c; - } - - @Override - public int updateInTransaction(Uri uri, ContentValues values, String selection, - String[] selectionArgs, boolean callerIsSyncAdapter) { - int match = matchUri(uri); - int rowsUpdated = 0; - SQLiteDatabase db = getDatabaseHelper().getWritableDatabase(); - if (match == MATCH_METADATA) { - rowsUpdated = modifyMetadata(db, values); - } else { - selection = addIdToSelection(match, selection); - selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs); - String table = getTableFromMatch(match, uri); - rowsUpdated = db.update(table, values, selection, selectionArgs); - } - postNotifyUri(uri); - return rowsUpdated; - } - - public void setMockNotification(ChangeNotification notification) { - mNotifier = notification; - } - - protected static String addIdToSelection(int match, String selection) { - String where; - switch (match) { - case MATCH_PHOTO_ID: - case MATCH_ALBUM_ID: - case MATCH_METADATA_ID: - where = WHERE_ID; - break; - default: - return selection; - } - return DatabaseUtils.concatenateWhere(selection, where); - } - - protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) { - String[] whereArgs; - switch (match) { - case MATCH_PHOTO_ID: - case MATCH_ALBUM_ID: - case MATCH_METADATA_ID: - whereArgs = new String[] { - uri.getPathSegments().get(1), - }; - break; - default: - return selectionArgs; - } - return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs); - } - - protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) { - List<String> segments = uri.getPathSegments(); - String[] additionalArgs = { - segments.get(1), - segments.get(2), - }; - - return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs); - } - - protected static String getTableFromMatch(int match, Uri uri) { - String table; - switch (match) { - case MATCH_PHOTO: - case MATCH_PHOTO_ID: - table = Photos.TABLE; - break; - case MATCH_ALBUM: - case MATCH_ALBUM_ID: - table = Albums.TABLE; - break; - case MATCH_METADATA: - case MATCH_METADATA_ID: - table = Metadata.TABLE; - break; - case MATCH_ACCOUNT: - case MATCH_ACCOUNT_ID: - table = Accounts.TABLE; - break; - default: - throw unknownUri(uri); - } - return table; - } - - @Override - public SQLiteOpenHelper getDatabaseHelper(Context context) { - return new PhotoDatabase(context, DB_NAME); - } - - private int modifyMetadata(SQLiteDatabase db, ContentValues values) { - int rowCount; - if (values.get(Metadata.VALUE) == null) { - String[] selectionArgs = { - values.getAsString(Metadata.PHOTO_ID), values.getAsString(Metadata.KEY), - }; - rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs); - } else { - long rowId = db.replace(Metadata.TABLE, null, values); - rowCount = (rowId == -1) ? 0 : 1; - } - return rowCount; - } - - private int matchUri(Uri uri) { - int match = sUriMatcher.match(uri); - if (match == UriMatcher.NO_MATCH) { - throw unknownUri(uri); - } - return match; - } - - @Override - protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) { - if (mNotifier != null) { - mNotifier.notifyChange(uri, syncToNetwork); - } else { - super.notifyChange(resolver, uri, syncToNetwork); - } - } - - protected static IllegalArgumentException unknownUri(Uri uri) { - return new IllegalArgumentException("Unknown Uri format: " + uri); - } - - protected static String nestWhere(String matchColumn, String table, String nestedWhere) { - String query = SQLiteQueryBuilder.buildQueryString(false, table, BASE_COLUMNS_ID, - nestedWhere, null, null, null, null); - return matchColumn + IN + NESTED_SELECT_START + query + NESTED_SELECT_END; - } - - protected static String metadataSelectionFromPhotos(String where) { - return nestWhere(Metadata.PHOTO_ID, Photos.TABLE, where); - } - - protected static String photoSelectionFromAlbums(String where) { - return nestWhere(Photos.ALBUM_ID, Albums.TABLE, where); - } - - protected static String photoSelectionFromAccounts(String where) { - return nestWhere(Photos.ACCOUNT_ID, Accounts.TABLE, where); - } - - protected static String albumSelectionFromAccounts(String where) { - return nestWhere(Albums.ACCOUNT_ID, Accounts.TABLE, where); - } - - protected int deleteCascade(Uri uri, int match, String selection, String[] selectionArgs) { - switch (match) { - case MATCH_PHOTO: - case MATCH_PHOTO_ID: - deleteCascade(Metadata.CONTENT_URI, MATCH_METADATA, - metadataSelectionFromPhotos(selection), selectionArgs); - break; - case MATCH_ALBUM: - case MATCH_ALBUM_ID: - deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO, - photoSelectionFromAlbums(selection), selectionArgs); - break; - case MATCH_ACCOUNT: - case MATCH_ACCOUNT_ID: - deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO, - photoSelectionFromAccounts(selection), selectionArgs); - deleteCascade(Albums.CONTENT_URI, MATCH_ALBUM, - albumSelectionFromAccounts(selection), selectionArgs); - break; - } - SQLiteDatabase db = getDatabaseHelper().getWritableDatabase(); - String table = getTableFromMatch(match, uri); - int deleted = db.delete(table, selection, selectionArgs); - if (deleted > 0) { - postNotifyUri(uri); - } - return deleted; - } - - private static void validateMatchTable(int match) { - switch (match) { - case MATCH_PHOTO: - case MATCH_ALBUM: - case MATCH_METADATA: - case MATCH_ACCOUNT: - break; - default: - throw new IllegalArgumentException("Operation not allowed on an existing row."); - } - } - - protected Cursor query(String table, String[] columns, String selection, - String[] selectionArgs, String orderBy, CancellationSignal cancellationSignal) { - SQLiteDatabase db = getDatabaseHelper().getReadableDatabase(); - if (ApiHelper.HAS_CANCELLATION_SIGNAL) { - return db.query(false, table, columns, selection, selectionArgs, null, null, - orderBy, null, cancellationSignal); - } else { - return db.query(table, columns, selection, selectionArgs, null, null, orderBy); - } - } - - protected static String[] replaceCount(String[] projection) { - if (projection != null && projection.length == 1 - && BaseColumns._COUNT.equals(projection[0])) { - return PROJECTION_COUNT; - } - return projection; - } -} diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java deleted file mode 100644 index 56c82c4a9..000000000 --- a/src/com/android/photos/data/PhotoSetLoader.java +++ /dev/null @@ -1,115 +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.CursorLoader; -import android.database.ContentObserver; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.MediaStore; -import android.provider.MediaStore.Files; -import android.provider.MediaStore.Files.FileColumns; - -import com.android.photos.drawables.DataUriThumbnailDrawable; -import com.android.photos.shims.LoaderCompatShim; - -import java.util.ArrayList; - -public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> { - - public static final String SUPPORTED_OPERATIONS = "supported_operations"; - - private static final Uri CONTENT_URI = Files.getContentUri("external"); - public static final String[] PROJECTION = new String[] { - FileColumns._ID, - FileColumns.DATA, - FileColumns.WIDTH, - FileColumns.HEIGHT, - FileColumns.DATE_ADDED, - FileColumns.MEDIA_TYPE, - SUPPORTED_OPERATIONS, - }; - - private static final String SORT_ORDER = FileColumns.DATE_ADDED + " DESC"; - private static final String SELECTION = - FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_IMAGE - + " OR " - + FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_VIDEO; - - public static final int INDEX_ID = 0; - public static final int INDEX_DATA = 1; - public static final int INDEX_WIDTH = 2; - public static final int INDEX_HEIGHT = 3; - public static final int INDEX_DATE_ADDED = 4; - public static final int INDEX_MEDIA_TYPE = 5; - public static final int INDEX_SUPPORTED_OPERATIONS = 6; - - private static final Uri GLOBAL_CONTENT_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/external/"); - private final ContentObserver mGlobalObserver = new ForceLoadContentObserver(); - - public PhotoSetLoader(Context context) { - super(context, CONTENT_URI, PROJECTION, SELECTION, null, SORT_ORDER); - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - getContext().getContentResolver().registerContentObserver(GLOBAL_CONTENT_URI, - true, mGlobalObserver); - } - - @Override - protected void onReset() { - super.onReset(); - getContext().getContentResolver().unregisterContentObserver(mGlobalObserver); - } - - @Override - public Drawable drawableForItem(Cursor item, Drawable recycle) { - DataUriThumbnailDrawable drawable = null; - if (recycle == null || !(recycle instanceof DataUriThumbnailDrawable)) { - drawable = new DataUriThumbnailDrawable(); - } else { - drawable = (DataUriThumbnailDrawable) recycle; - } - drawable.setImage(item.getString(INDEX_DATA), - item.getInt(INDEX_WIDTH), item.getInt(INDEX_HEIGHT)); - return drawable; - } - - @Override - public Uri uriForItem(Cursor item) { - return null; - } - - @Override - public ArrayList<Uri> urisForSubItems(Cursor item) { - return null; - } - - @Override - public void deleteItemWithPath(Object path) { - - } - - @Override - public Object getPathForItem(Cursor item) { - return null; - } -} diff --git a/src/com/android/photos/data/SQLiteContentProvider.java b/src/com/android/photos/data/SQLiteContentProvider.java deleted file mode 100644 index daffa6e79..000000000 --- a/src/com/android/photos/data/SQLiteContentProvider.java +++ /dev/null @@ -1,265 +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.ContentProvider; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * General purpose {@link ContentProvider} base class that uses SQLiteDatabase - * for storage. - */ -public abstract class SQLiteContentProvider extends ContentProvider { - - @SuppressWarnings("unused") - private static final String TAG = "SQLiteContentProvider"; - - private SQLiteOpenHelper mOpenHelper; - private Set<Uri> mChangedUris; - - private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>(); - private static final int SLEEP_AFTER_YIELD_DELAY = 4000; - - /** - * Maximum number of operations allowed in a batch between yield points. - */ - private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500; - - @Override - public boolean onCreate() { - Context context = getContext(); - mOpenHelper = getDatabaseHelper(context); - mChangedUris = new HashSet<Uri>(); - return true; - } - - @Override - public void shutdown() { - getDatabaseHelper().close(); - } - - /** - * Returns a {@link SQLiteOpenHelper} that can open the database. - */ - public abstract SQLiteOpenHelper getDatabaseHelper(Context context); - - /** - * The equivalent of the {@link #insert} method, but invoked within a - * transaction. - */ - public abstract Uri insertInTransaction(Uri uri, ContentValues values, - boolean callerIsSyncAdapter); - - /** - * The equivalent of the {@link #update} method, but invoked within a - * transaction. - */ - public abstract int updateInTransaction(Uri uri, ContentValues values, String selection, - String[] selectionArgs, boolean callerIsSyncAdapter); - - /** - * The equivalent of the {@link #delete} method, but invoked within a - * transaction. - */ - public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, - boolean callerIsSyncAdapter); - - /** - * Call this to add a URI to the list of URIs to be notified when the - * transaction is committed. - */ - protected void postNotifyUri(Uri uri) { - synchronized (mChangedUris) { - mChangedUris.add(uri); - } - } - - public boolean isCallerSyncAdapter(Uri uri) { - return false; - } - - public SQLiteOpenHelper getDatabaseHelper() { - return mOpenHelper; - } - - private boolean applyingBatch() { - return mApplyingBatch.get() != null && mApplyingBatch.get(); - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - Uri result = null; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - boolean applyingBatch = applyingBatch(); - if (!applyingBatch) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - result = insertInTransaction(uri, values, callerIsSyncAdapter); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - } else { - result = insertInTransaction(uri, values, callerIsSyncAdapter); - } - return result; - } - - @Override - public int bulkInsert(Uri uri, ContentValues[] values) { - int numValues = values.length; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - for (int i = 0; i < numValues; i++) { - @SuppressWarnings("unused") - Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter); - db.yieldIfContendedSafely(); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - return numValues; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - int count = 0; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - boolean applyingBatch = applyingBatch(); - if (!applyingBatch) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - count = updateInTransaction(uri, values, selection, selectionArgs, - callerIsSyncAdapter); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - } else { - count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter); - } - - return count; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - int count = 0; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - boolean applyingBatch = applyingBatch(); - if (!applyingBatch) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - } else { - count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter); - } - return count; - } - - @Override - public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) - throws OperationApplicationException { - int ypCount = 0; - int opCount = 0; - boolean callerIsSyncAdapter = false; - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - mApplyingBatch.set(true); - final int numOperations = operations.size(); - final ContentProviderResult[] results = new ContentProviderResult[numOperations]; - for (int i = 0; i < numOperations; i++) { - if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) { - throw new OperationApplicationException( - "Too many content provider operations between yield points. " - + "The maximum number of operations per yield point is " - + MAX_OPERATIONS_PER_YIELD_POINT, ypCount); - } - final ContentProviderOperation operation = operations.get(i); - if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) { - callerIsSyncAdapter = true; - } - if (i > 0 && operation.isYieldAllowed()) { - opCount = 0; - if (db.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) { - ypCount++; - } - } - results[i] = operation.apply(this, results, i); - } - db.setTransactionSuccessful(); - return results; - } finally { - mApplyingBatch.set(false); - db.endTransaction(); - onEndTransaction(callerIsSyncAdapter); - } - } - - protected Set<Uri> onEndTransaction(boolean callerIsSyncAdapter) { - Set<Uri> changed; - synchronized (mChangedUris) { - changed = new HashSet<Uri>(mChangedUris); - mChangedUris.clear(); - } - ContentResolver resolver = getContext().getContentResolver(); - for (Uri uri : changed) { - boolean syncToNetwork = !callerIsSyncAdapter && syncToNetwork(uri); - notifyChange(resolver, uri, syncToNetwork); - } - return changed; - } - - protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) { - resolver.notifyChange(uri, null, syncToNetwork); - } - - protected boolean syncToNetwork(Uri uri) { - return false; - } -}
\ No newline at end of file diff --git a/src/com/android/photos/data/SparseArrayBitmapPool.java b/src/com/android/photos/data/SparseArrayBitmapPool.java deleted file mode 100644 index 95e10267b..000000000 --- a/src/com/android/photos/data/SparseArrayBitmapPool.java +++ /dev/null @@ -1,212 +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.util.SparseArray; - -import android.util.Pools.Pool; -import android.util.Pools.SimplePool; - -/** - * Bitmap pool backed by a sparse array indexing linked lists of bitmaps - * sharing the same width. Performance will degrade if using this to store - * many bitmaps with the same width but many different heights. - */ -public class SparseArrayBitmapPool { - - private int mCapacityBytes; - private SparseArray<Node> mStore = new SparseArray<Node>(); - private int mSizeBytes = 0; - - private Pool<Node> mNodePool; - private Node mPoolNodesHead = null; - private Node mPoolNodesTail = null; - - protected static class Node { - Bitmap bitmap; - - // Each node is part of two doubly linked lists: - // - A pool-level list (accessed by mPoolNodesHead and mPoolNodesTail) - // that is used for FIFO eviction of nodes when the pool gets full. - // - A bucket-level list for each index of the sparse array, so that - // each index can store more than one item. - Node prevInBucket; - Node nextInBucket; - Node nextInPool; - Node prevInPool; - } - - /** - * @param capacityBytes Maximum capacity of the pool in bytes. - * @param nodePool Shared pool to use for recycling linked list nodes, or null. - */ - public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) { - mCapacityBytes = capacityBytes; - if (nodePool == null) { - mNodePool = new SimplePool<Node>(32); - } else { - mNodePool = nodePool; - } - } - - /** - * Set the maximum capacity of the pool, and if necessary trim it down to size. - */ - public synchronized void setCapacity(int capacityBytes) { - mCapacityBytes = capacityBytes; - - // No-op unless current size exceeds the new capacity. - freeUpCapacity(0); - } - - private void freeUpCapacity(int bytesNeeded) { - int targetSize = mCapacityBytes - bytesNeeded; - // Repeatedly remove the oldest node until we have freed up at least bytesNeeded. - while (mPoolNodesTail != null && mSizeBytes > targetSize) { - unlinkAndRecycleNode(mPoolNodesTail, true); - } - } - - private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) { - // Unlink the node from its sparse array bucket list. - if (n.prevInBucket != null) { - // This wasn't the head, update the previous node. - n.prevInBucket.nextInBucket = n.nextInBucket; - } else { - // This was the head of the bucket, replace it with the next node. - mStore.put(n.bitmap.getWidth(), n.nextInBucket); - } - if (n.nextInBucket != null) { - // This wasn't the tail, update the next node. - n.nextInBucket.prevInBucket = n.prevInBucket; - } - - // Unlink the node from the pool-wide list. - if (n.prevInPool != null) { - // This wasn't the head, update the previous node. - n.prevInPool.nextInPool = n.nextInPool; - } else { - // This was the head of the pool-wide list, update the head pointer. - mPoolNodesHead = n.nextInPool; - } - if (n.nextInPool != null) { - // This wasn't the tail, update the next node. - n.nextInPool.prevInPool = n.prevInPool; - } else { - // This was the tail, update the tail pointer. - mPoolNodesTail = n.prevInPool; - } - - // Recycle the node. - n.nextInBucket = null; - n.nextInPool = null; - n.prevInBucket = null; - n.prevInPool = null; - mSizeBytes -= n.bitmap.getByteCount(); - if (recycleBitmap) n.bitmap.recycle(); - n.bitmap = null; - mNodePool.release(n); - } - - /** - * @return Capacity of the pool in bytes. - */ - public synchronized int getCapacity() { - return mCapacityBytes; - } - - /** - * @return Total size in bytes of the bitmaps stored in the pool. - */ - public synchronized int getSize() { - return mSizeBytes; - } - - /** - * @return Bitmap from the pool with the desired height/width or null if none available. - */ - public synchronized Bitmap get(int width, int height) { - Node cur = mStore.get(width); - - // Traverse the list corresponding to the width bucket in the - // sparse array, and unlink and return the first bitmap that - // also has the correct height. - while (cur != null) { - if (cur.bitmap.getHeight() == height) { - Bitmap b = cur.bitmap; - unlinkAndRecycleNode(cur, false); - return b; - } - cur = cur.nextInBucket; - } - return null; - } - - /** - * Adds the given bitmap to the pool. - * @return Whether the bitmap was added to the pool. - */ - public synchronized boolean put(Bitmap b) { - if (b == null) { - return false; - } - - // Ensure there is enough room to contain the new bitmap. - int bytes = b.getByteCount(); - freeUpCapacity(bytes); - - Node newNode = mNodePool.acquire(); - if (newNode == null) { - newNode = new Node(); - } - newNode.bitmap = b; - - // We append to the head, and freeUpCapacity clears from the tail, - // resulting in FIFO eviction. - newNode.prevInBucket = null; - newNode.prevInPool = null; - newNode.nextInPool = mPoolNodesHead; - mPoolNodesHead = newNode; - - // Insert the node into its appropriate bucket based on width. - int key = b.getWidth(); - newNode.nextInBucket = mStore.get(key); - if (newNode.nextInBucket != null) { - // The bucket already had nodes, update the old head. - newNode.nextInBucket.prevInBucket = newNode; - } - mStore.put(key, newNode); - - if (newNode.nextInPool == null) { - // This is the only node in the list, update the tail pointer. - mPoolNodesTail = newNode; - } else { - newNode.nextInPool.prevInPool = newNode; - } - mSizeBytes += bytes; - return true; - } - - /** - * Empty the pool, recycling all the bitmaps currently in it. - */ - public synchronized void clear() { - // Clearing is equivalent to ensuring all the capacity is available. - freeUpCapacity(mCapacityBytes); - } -} |