summaryrefslogtreecommitdiffstats
path: root/src/com/android/photos/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/photos/data')
-rw-r--r--src/com/android/photos/data/AlbumSetLoader.java54
-rw-r--r--src/com/android/photos/data/BitmapDecoder.java224
-rw-r--r--src/com/android/photos/data/FileRetriever.java109
-rw-r--r--src/com/android/photos/data/GalleryBitmapPool.java161
-rw-r--r--src/com/android/photos/data/MediaCache.java676
-rw-r--r--src/com/android/photos/data/MediaCacheDatabase.java286
-rw-r--r--src/com/android/photos/data/MediaCacheUtils.java167
-rw-r--r--src/com/android/photos/data/MediaRetriever.java129
-rw-r--r--src/com/android/photos/data/NotificationWatcher.java55
-rw-r--r--src/com/android/photos/data/PhotoDatabase.java195
-rw-r--r--src/com/android/photos/data/PhotoProvider.java536
-rw-r--r--src/com/android/photos/data/PhotoSetLoader.java115
-rw-r--r--src/com/android/photos/data/SQLiteContentProvider.java265
-rw-r--r--src/com/android/photos/data/SparseArrayBitmapPool.java212
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);
- }
-}