summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera
diff options
context:
space:
mode:
authorAngus Kong <shkong@google.com>2013-05-15 00:18:23 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-05-15 00:18:23 +0000
commit2b06ff267ca205b72b79ac9b6f8089a623719afe (patch)
tree788ffceae428838a0d466cafd3ac4089987427fb /src/com/android/camera
parent7e072be494fa0c46b1f0aeda4cdbf35e8d55fbf5 (diff)
parent750e8ec8af168afd318d47082b326b95f1cca517 (diff)
downloadandroid_packages_apps_Snap-2b06ff267ca205b72b79ac9b6f8089a623719afe.tar.gz
android_packages_apps_Snap-2b06ff267ca205b72b79ac9b6f8089a623719afe.tar.bz2
android_packages_apps_Snap-2b06ff267ca205b72b79ac9b6f8089a623719afe.zip
Merge "Improve bitmap load efficiency." into gb-ub-photos-carlsbad
Diffstat (limited to 'src/com/android/camera')
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java419
-rw-r--r--src/com/android/camera/data/LocalData.java421
-rw-r--r--src/com/android/camera/ui/FilmStripView.java40
3 files changed, 524 insertions, 356 deletions
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index cb67aacd7..1fb9465a6 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -19,20 +19,13 @@ package com.android.camera.data;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
-import android.media.MediaMetadataRetriever;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Video;
-import android.provider.MediaStore.Video.VideoColumns;
import android.util.Log;
import android.view.View;
-import android.widget.ImageView;
import com.android.camera.Storage;
import com.android.camera.ui.FilmStripView;
@@ -40,11 +33,10 @@ import com.android.camera.ui.FilmStripView.ImageData;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
/**
- * A FilmStripDataProvider that provide data in the camera folder.
+ * A FilmStrip.DataProvider that provide data in the camera folder.
*
* The given view for camera preview won't be added until the preview info
* has been set by setCameraPreviewInfo(int, int).
@@ -80,18 +72,22 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
@Override
public int getTotalNumber() {
- if (mImages == null) return 0;
+ if (mImages == null) {
+ return 0;
+ }
return mImages.size();
}
@Override
public ImageData getImageData(int id) {
- if (mImages == null || id >= mImages.size()) return null;
+ if (mImages == null || id >= mImages.size() || id < 0) {
+ return null;
+ }
return mImages.get(id);
}
@Override
- public void suggestSize(int w, int h) {
+ public void suggestDecodeSize(int w, int h) {
if (w <= 0 || h <= 0) {
mSuggestedWidth = mSuggestedHeight = DEFAULT_DECODE_SIZE;
} else {
@@ -102,7 +98,9 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
@Override
public View getView(Context c, int dataID) {
- if (mImages == null) return null;
+ if (mImages == null) {
+ return null;
+ }
if (dataID >= mImages.size() || dataID < 0) {
return null;
}
@@ -114,7 +112,9 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
@Override
public void setListener(Listener listener) {
mListener = listener;
- if (mImages != null) mListener.onDataLoaded();
+ if (mImages != null) {
+ mListener.onDataLoaded();
+ }
}
private LocalData buildCameraImageData(int width, int height) {
@@ -123,11 +123,15 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
}
private void addOrReplaceCameraData(LocalData data) {
- if (mImages == null) mImages = new ArrayList<LocalData>();
+ if (mImages == null) {
+ mImages = new ArrayList<LocalData>();
+ }
if (mImages.size() == 0) {
// No data at all.
mImages.add(0, data);
- if (mListener != null) mListener.onDataLoaded();
+ if (mListener != null) {
+ mListener.onDataLoaded();
+ }
return;
}
@@ -144,7 +148,9 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
@Override
public boolean isDataUpdated(int id) {
- if (id == 0) return true;
+ if (id == 0) {
+ return true;
+ }
return false;
}
});
@@ -165,51 +171,60 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
// Photos
Cursor c = resolver[0].query(
Images.Media.EXTERNAL_CONTENT_URI,
- LocalPhotoData.QUERY_PROJECTION,
+ LocalData.Photo.QUERY_PROJECTION,
MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
- LocalPhotoData.QUERY_ORDER);
+ LocalData.Photo.QUERY_ORDER);
if (c != null && c.moveToFirst()) {
// build up the list.
while (true) {
- LocalData data = LocalPhotoData.buildFromCursor(c);
+ LocalData data = LocalData.Photo.buildFromCursor(c);
if (data != null) {
l.add(data);
} else {
Log.e(TAG, "Error loading data:"
- + c.getString(LocalPhotoData.COL_DATA));
+ + c.getString(LocalData.Photo.COL_DATA));
+ }
+ if (c.isLast()) {
+ break;
}
- if (c.isLast()) break;
c.moveToNext();
}
}
- if (c != null) c.close();
+ if (c != null) {
+ c.close();
+ }
c = resolver[0].query(
Video.Media.EXTERNAL_CONTENT_URI,
- LocalVideoData.QUERY_PROJECTION,
+ LocalData.Video.QUERY_PROJECTION,
MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH,
- LocalVideoData.QUERY_ORDER);
+ LocalData.Video.QUERY_ORDER);
if (c != null && c.moveToFirst()) {
// build up the list.
c.moveToFirst();
while (true) {
- LocalData data = LocalVideoData.buildFromCursor(c);
+ LocalData data = LocalData.Video.buildFromCursor(c);
if (data != null) {
l.add(data);
Log.v(TAG, "video data added:" + data);
} else {
Log.e(TAG, "Error loading data:"
- + c.getString(LocalVideoData.COL_DATA));
+ + c.getString(LocalData.Video.COL_DATA));
+ }
+ if (!c.isLast()) {
+ c.moveToNext();
+ } else {
+ break;
}
- if (!c.isLast()) c.moveToNext();
- else break;
}
}
- if (c != null) c.close();
+ if (c != null) {
+ c.close();
+ }
if (l.size() == 0) return null;
- Collections.sort(l);
+ Collections.sort(l, new LocalData.NewestFirstComparator());
return l;
}
@@ -226,8 +241,10 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
mImages = l;
if (cameraData != null) {
- // camera view exists, so we make sure at least have 1 data in the list.
- if (mImages == null) mImages = new ArrayList<LocalData>();
+ // camera view exists, so we make sure at least 1 data is in the list.
+ if (mImages == null) {
+ mImages = new ArrayList<LocalData>();
+ }
mImages.add(0, cameraData);
if (mListener != null) {
// Only the camera data is not changed, everything else is changed.
@@ -246,64 +263,37 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
}
} else {
// both might be null.
- if (changed) mListener.onDataLoaded();
+ if (changed) {
+ mListener.onDataLoaded();
+ }
}
}
}
- private abstract static class LocalData implements
- FilmStripView.ImageData,
- Comparable<LocalData> {
- public long id;
- public String title;
- public String mimeType;
- public long dateTaken;
- public long dateModified;
- public String path;
- // width and height should be adjusted according to orientation.
- public int width;
- public int height;
-
- @Override
- public int getWidth() {
- return width;
- }
+ private class CameraPreviewData implements LocalData {
+ private int width;
+ private int height;
- @Override
- public int getHeight() {
- return height;
+ CameraPreviewData(int w, int h) {
+ width = w;
+ height = h;
}
@Override
- public boolean isActionSupported(int action) {
- return false;
- }
-
- private int compare(long v1, long v2) {
- return ((v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0));
+ public long getDateTaken() {
+ // This value is used for sorting.
+ return -1;
}
@Override
- public int compareTo(LocalData d) {
- int cmp = compare(d.dateTaken, dateTaken);
- if (cmp != 0) return cmp;
- cmp = compare(d.dateModified, dateModified);
- if (cmp != 0) return cmp;
- cmp = d.title.compareTo(title);
- if (cmp != 0) return cmp;
- return compare(d.id, id);
+ public long getDateModified() {
+ // This value might be used for sorting.
+ return -1;
}
@Override
- public abstract int getType();
-
- abstract View getView(Context c, int width, int height, Drawable placeHolder);
- }
-
- private class CameraPreviewData extends LocalData {
- CameraPreviewData(int w, int h) {
- width = w;
- height = h;
+ public String getTitle() {
+ return "";
}
@Override
@@ -322,283 +312,24 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
}
@Override
- View getView(Context c, int width, int height, Drawable placeHolder) {
- return mCameraPreviewView;
- }
- }
-
- private static class LocalPhotoData extends LocalData {
- static final String QUERY_ORDER = ImageColumns.DATE_TAKEN + " DESC, "
- + ImageColumns._ID + " DESC";
- static final String[] QUERY_PROJECTION = {
- ImageColumns._ID, // 0, int
- ImageColumns.TITLE, // 1, string
- ImageColumns.MIME_TYPE, // 2, string
- ImageColumns.DATE_TAKEN, // 3, int
- ImageColumns.DATE_MODIFIED, // 4, int
- ImageColumns.DATA, // 5, string
- ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
- ImageColumns.WIDTH, // 7, int
- ImageColumns.HEIGHT, // 8, int
- };
-
- private static final int COL_ID = 0;
- private static final int COL_TITLE = 1;
- private static final int COL_MIME_TYPE = 2;
- private static final int COL_DATE_TAKEN = 3;
- private static final int COL_DATE_MODIFIED = 4;
- private static final int COL_DATA = 5;
- private static final int COL_ORIENTATION = 6;
- private static final int COL_WIDTH = 7;
- private static final int COL_HEIGHT = 8;
-
- // 32K buffer.
- private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
-
- // from MediaStore, can only be 0, 90, 180, 270;
- public int orientation;
-
- static LocalPhotoData buildFromCursor(Cursor c) {
- LocalPhotoData d = new LocalPhotoData();
- d.id = c.getLong(COL_ID);
- d.title = c.getString(COL_TITLE);
- d.mimeType = c.getString(COL_MIME_TYPE);
- d.dateTaken = c.getLong(COL_DATE_TAKEN);
- d.dateModified = c.getLong(COL_DATE_MODIFIED);
- d.path = c.getString(COL_DATA);
- d.orientation = c.getInt(COL_ORIENTATION);
- d.width = c.getInt(COL_WIDTH);
- d.height = c.getInt(COL_HEIGHT);
- if (d.width <= 0 || d.height <= 0) {
- Log.v(TAG, "warning! zero dimension for "
- + d.path + ":" + d.width + "x" + d.height);
- Dimension dim = decodeDimension(d.path);
- if (dim != null) {
- d.width = dim.width;
- d.height = dim.height;
- } else {
- Log.v(TAG, "warning! dimension decode failed for " + d.path);
- Bitmap b = BitmapFactory.decodeFile(d.path);
- if (b == null) return null;
- d.width = b.getWidth();
- d.height = b.getHeight();
- }
- }
- if (d.orientation == 90 || d.orientation == 270) {
- int b = d.width;
- d.width = d.height;
- d.height = b;
- }
- return d;
+ public boolean isActionSupported(int action) {
+ return false;
}
@Override
- View getView(Context c,
- int decodeWidth, int decodeHeight, Drawable placeHolder) {
- ImageView v = new ImageView(c);
- v.setImageDrawable(placeHolder);
-
- v.setScaleType(ImageView.ScaleType.FIT_XY);
- LoadBitmapTask task = new LoadBitmapTask(v, decodeWidth, decodeHeight);
- task.execute();
- return v;
+ public View getView(Context c, int width, int height, Drawable placeHolder) {
+ return mCameraPreviewView;
}
@Override
- public String toString() {
- return "LocalPhotoData:" + ",data=" + path + ",mimeType=" + mimeType
- + "," + width + "x" + height + ",orientation=" + orientation
- + ",date=" + new Date(dateTaken);
+ public void prepare() {
+ // do nothing.
}
@Override
- public int getType() {
- return TYPE_PHOTO;
- }
-
- private static Dimension decodeDimension(String path) {
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inJustDecodeBounds = true;
- Bitmap b = BitmapFactory.decodeFile(path, opts);
- if (b == null) return null;
- Dimension d = new Dimension();
- d.width = opts.outWidth;
- d.height = opts.outHeight;
- return d;
- }
-
- private static class Dimension {
- public int width;
- public int height;
- }
-
- private class LoadBitmapTask extends AsyncTask<Void, Void, Bitmap> {
- private ImageView mView;
- private int mDecodeWidth;
- private int mDecodeHeight;
-
- public LoadBitmapTask(ImageView v, int decodeWidth, int decodeHeight) {
- mView = v;
- mDecodeWidth = decodeWidth;
- mDecodeHeight = decodeHeight;
- }
-
- @Override
- protected Bitmap doInBackground(Void... v) {
- BitmapFactory.Options opts = null;
- Bitmap b;
- int sample = 1;
- while (mDecodeWidth * sample < width
- || mDecodeHeight * sample < height) {
- sample *= 2;
- }
- opts = new BitmapFactory.Options();
- opts.inSampleSize = sample;
- opts.inTempStorage = DECODE_TEMP_STORAGE;
- if (isCancelled()) return null;
- b = BitmapFactory.decodeFile(path, opts);
- if (orientation != 0) {
- if (isCancelled()) return null;
- Matrix m = new Matrix();
- m.setRotate((float) orientation);
- b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
- }
- return b;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Cannot decode bitmap file:" + path);
- return;
- }
- mView.setScaleType(ImageView.ScaleType.FIT_XY);
- mView.setImageBitmap(bitmap);
- }
+ public void recycle() {
+ // do nothing.
}
}
- private static class LocalVideoData extends LocalData {
- static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, "
- + VideoColumns._ID + " DESC";
- static final String[] QUERY_PROJECTION = {
- VideoColumns._ID, // 0, int
- VideoColumns.TITLE, // 1, string
- VideoColumns.MIME_TYPE, // 2, string
- VideoColumns.DATE_TAKEN, // 3, int
- VideoColumns.DATE_MODIFIED, // 4, int
- VideoColumns.DATA, // 5, string
- VideoColumns.WIDTH, // 6, int
- VideoColumns.HEIGHT, // 7, int
- VideoColumns.RESOLUTION
- };
-
- private static final int COL_ID = 0;
- private static final int COL_TITLE = 1;
- private static final int COL_MIME_TYPE = 2;
- private static final int COL_DATE_TAKEN = 3;
- private static final int COL_DATE_MODIFIED = 4;
- private static final int COL_DATA = 5;
- private static final int COL_WIDTH = 6;
- private static final int COL_HEIGHT = 7;
-
- public int resolutionW;
- public int resolutionH;
-
- static LocalVideoData buildFromCursor(Cursor c) {
- LocalVideoData d = new LocalVideoData();
- d.id = c.getLong(COL_ID);
- d.title = c.getString(COL_TITLE);
- d.mimeType = c.getString(COL_MIME_TYPE);
- d.dateTaken = c.getLong(COL_DATE_TAKEN);
- d.dateModified = c.getLong(COL_DATE_MODIFIED);
- d.path = c.getString(COL_DATA);
- d.width = c.getInt(COL_WIDTH);
- d.height = c.getInt(COL_HEIGHT);
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- retriever.setDataSource(d.path);
- String rotation = retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
- if (d.width == 0 || d.height == 0) {
- d.width = Integer.parseInt(retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
- d.height = Integer.parseInt(retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
- }
- retriever.release();
- if (rotation.equals("90") || rotation.equals("270")) {
- int b = d.width;
- d.width = d.height;
- d.height = b;
- }
- return d;
- }
-
- @Override
- View getView(Context c,
- int decodeWidth, int decodeHeight, Drawable placeHolder) {
- ImageView v = new ImageView(c);
- v.setImageDrawable(placeHolder);
-
- v.setScaleType(ImageView.ScaleType.FIT_XY);
- LoadBitmapTask task = new LoadBitmapTask(v);
- task.execute();
- return v;
- }
-
-
- @Override
- public String toString() {
- return "LocalVideoData:" + ",data=" + path + ",mimeType=" + mimeType
- + "," + width + "x" + height + ",date=" + new Date(dateTaken);
- }
-
- @Override
- public int getType() {
- return TYPE_PHOTO;
- }
-
- private static Dimension decodeDimension(String path) {
- Dimension d = new Dimension();
- return d;
- }
-
- private static class Dimension {
- public int width;
- public int height;
- }
-
- private class LoadBitmapTask extends AsyncTask<Void, Void, Bitmap> {
- private ImageView mView;
-
- public LoadBitmapTask(ImageView v) {
- mView = v;
- }
-
- @Override
- protected Bitmap doInBackground(Void... v) {
- android.media.MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- retriever.setDataSource(path);
- byte[] data = retriever.getEmbeddedPicture();
- Bitmap bitmap = null;
- if (data != null) {
- bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- }
- if (bitmap == null) {
- bitmap = (Bitmap) retriever.getFrameAtTime();
- }
- retriever.release();
- return bitmap;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Cannot decode video file:" + path);
- return;
- }
- mView.setImageBitmap(bitmap);
- }
- }
- }
}
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
new file mode 100644
index 000000000..1fda9eb67
--- /dev/null
+++ b/src/com/android/camera/data/LocalData.java
@@ -0,0 +1,421 @@
+/*
+ * 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.camera.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.media.MediaMetadataRetriever;
+import android.os.AsyncTask;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.camera.ui.FilmStripView;
+
+import java.util.Comparator;
+import java.util.Date;
+
+/* An abstract interface that represents the local media data. Also implements
+ * Comparable interface so we can sort in DataAdapter.
+ */
+abstract interface LocalData extends FilmStripView.ImageData {
+ static final String TAG = "LocalData";
+
+ abstract View getView(Context c, int width, int height, Drawable placeHolder);
+ abstract long getDateTaken();
+ abstract long getDateModified();
+ abstract String getTitle();
+
+ static class NewestFirstComparator implements Comparator<LocalData> {
+
+ private static int compare(long v1, long v2) {
+ if (v1 == -1) {
+ if (v2 == -1) return 0;
+ return -1;
+ }
+ if (v2 == -1) return 0;
+
+ return ((v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0));
+ }
+
+ @Override
+ public int compare(LocalData d1, LocalData d2) {
+ int cmp = compare(d1.getDateTaken(), d2.getDateTaken());
+ if (cmp == 0) {
+ cmp = compare(d1.getDateModified(), d2.getDateModified());
+ }
+ if (cmp == 0) {
+ cmp = d1.getTitle().compareTo(d2.getTitle());
+ }
+ return cmp;
+ }
+ }
+
+ /*
+ * A base class for all the local media files. The bitmap is loaded in background
+ * thread. Subclasses should implement their own background loading thread by
+ * subclassing BitmapLoadTask and overriding doInBackground() to return a bitmap.
+ */
+ abstract static class LocalMediaData implements LocalData {
+ protected long id;
+ protected String title;
+ protected String mimeType;
+ protected long dateTaken;
+ protected long dateModified;
+ protected String path;
+ // width and height should be adjusted according to orientation.
+ protected int width;
+ protected int height;
+
+ // true if this data has a corresponding visible view.
+ protected Boolean mUsing = false;
+
+ @Override
+ public long getDateTaken() {
+ return dateTaken;
+ }
+
+ @Override
+ public long getDateModified() {
+ return dateModified;
+ }
+
+ @Override
+ public String getTitle() {
+ return new String(title);
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public boolean isActionSupported(int action) {
+ return false;
+ }
+
+ @Override
+ public View getView(Context c,
+ int decodeWidth, int decodeHeight, Drawable placeHolder) {
+ ImageView v = new ImageView(c);
+ v.setImageDrawable(placeHolder);
+
+ v.setScaleType(ImageView.ScaleType.FIT_XY);
+ BitmapLoadTask task = getBitmapLoadTask(v, decodeWidth, decodeHeight);
+ task.execute();
+ return v;
+ }
+
+ @Override
+ public void prepare() {
+ synchronized (mUsing) {
+ mUsing = true;
+ }
+ }
+
+ @Override
+ public void recycle() {
+ synchronized (mUsing) {
+ mUsing = false;
+ }
+ }
+
+ protected boolean isUsing() {
+ synchronized (mUsing) {
+ return mUsing;
+ }
+ }
+
+ @Override
+ public abstract int getType();
+
+ protected abstract BitmapLoadTask getBitmapLoadTask(
+ ImageView v, int decodeWidth, int decodeHeight);
+
+ /*
+ * An AsyncTask class that loads the bitmap in the background thread.
+ * Sub-classes should implement their own "protected Bitmap doInBackground(Void... )"
+ */
+ protected abstract class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {
+ protected ImageView mView;
+
+ protected BitmapLoadTask(ImageView v) {
+ mView = v;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (!isUsing()) return;
+ if (bitmap == null) {
+ Log.e(TAG, "Failed decoding bitmap for file:" + path);
+ return;
+ }
+ mView.setScaleType(ImageView.ScaleType.FIT_XY);
+ mView.setImageBitmap(bitmap);
+ }
+ }
+ }
+
+ static class Photo extends LocalMediaData {
+ public static final int COL_ID = 0;
+ public static final int COL_TITLE = 1;
+ public static final int COL_MIME_TYPE = 2;
+ public static final int COL_DATE_TAKEN = 3;
+ public static final int COL_DATE_MODIFIED = 4;
+ public static final int COL_DATA = 5;
+ public static final int COL_ORIENTATION = 6;
+ public static final int COL_WIDTH = 7;
+ public static final int COL_HEIGHT = 8;
+
+ static final String QUERY_ORDER = ImageColumns.DATE_TAKEN + " DESC, "
+ + ImageColumns._ID + " DESC";
+ static final String[] QUERY_PROJECTION = {
+ ImageColumns._ID, // 0, int
+ ImageColumns.TITLE, // 1, string
+ ImageColumns.MIME_TYPE, // 2, string
+ ImageColumns.DATE_TAKEN, // 3, int
+ ImageColumns.DATE_MODIFIED, // 4, int
+ ImageColumns.DATA, // 5, string
+ ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
+ ImageColumns.WIDTH, // 7, int
+ ImageColumns.HEIGHT, // 8, int
+ };
+
+ // 32K buffer.
+ private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
+
+ // from MediaStore, can only be 0, 90, 180, 270;
+ public int orientation;
+
+ static Photo buildFromCursor(Cursor c) {
+ Photo d = new Photo();
+ d.id = c.getLong(COL_ID);
+ d.title = c.getString(COL_TITLE);
+ d.mimeType = c.getString(COL_MIME_TYPE);
+ d.dateTaken = c.getLong(COL_DATE_TAKEN);
+ d.dateModified = c.getLong(COL_DATE_MODIFIED);
+ d.path = c.getString(COL_DATA);
+ d.orientation = c.getInt(COL_ORIENTATION);
+ d.width = c.getInt(COL_WIDTH);
+ d.height = c.getInt(COL_HEIGHT);
+ if (d.width <= 0 || d.height <= 0) {
+ Log.v(TAG, "warning! zero dimension for "
+ + d.path + ":" + d.width + "x" + d.height);
+ BitmapFactory.Options opts = decodeDimension(d.path);
+ if (opts != null) {
+ d.width = opts.outWidth;
+ d.height = opts.outHeight;
+ } else {
+ Log.v(TAG, "warning! dimension decode failed for " + d.path);
+ Bitmap b = BitmapFactory.decodeFile(d.path);
+ if (b == null) {
+ return null;
+ }
+ d.width = b.getWidth();
+ d.height = b.getHeight();
+ }
+ }
+ if (d.orientation == 90 || d.orientation == 270) {
+ int b = d.width;
+ d.width = d.height;
+ d.height = b;
+ }
+ return d;
+ }
+
+ @Override
+ public String toString() {
+ return "Photo:" + ",data=" + path + ",mimeType=" + mimeType
+ + "," + width + "x" + height + ",orientation=" + orientation
+ + ",date=" + new Date(dateTaken);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PHOTO;
+ }
+
+ @Override
+ protected BitmapLoadTask getBitmapLoadTask(
+ ImageView v, int decodeWidth, int decodeHeight) {
+ return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight);
+ }
+
+ private static BitmapFactory.Options decodeDimension(String path) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inJustDecodeBounds = true;
+ Bitmap b = BitmapFactory.decodeFile(path, opts);
+ if (b == null) {
+ return null;
+ }
+ return opts;
+ }
+
+ private final class PhotoBitmapLoadTask extends BitmapLoadTask {
+ private int mDecodeWidth;
+ private int mDecodeHeight;
+
+ public PhotoBitmapLoadTask(ImageView v, int decodeWidth, int decodeHeight) {
+ super(v);
+ mDecodeWidth = decodeWidth;
+ mDecodeHeight = decodeHeight;
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... v) {
+ BitmapFactory.Options opts = null;
+ Bitmap b;
+ int sample = 1;
+ while (mDecodeWidth * sample < width
+ || mDecodeHeight * sample < height) {
+ sample *= 2;
+ }
+ opts = new BitmapFactory.Options();
+ opts.inSampleSize = sample;
+ opts.inTempStorage = DECODE_TEMP_STORAGE;
+ if (isCancelled() || !isUsing()) {
+ return null;
+ }
+ b = BitmapFactory.decodeFile(path, opts);
+ if (orientation != 0) {
+ if (isCancelled() || !isUsing()) {
+ return null;
+ }
+ Matrix m = new Matrix();
+ m.setRotate((float) orientation);
+ b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+ }
+ return b;
+ }
+ }
+ }
+
+ static class Video extends LocalMediaData {
+ public static final int COL_ID = 0;
+ public static final int COL_TITLE = 1;
+ public static final int COL_MIME_TYPE = 2;
+ public static final int COL_DATE_TAKEN = 3;
+ public static final int COL_DATE_MODIFIED = 4;
+ public static final int COL_DATA = 5;
+ public static final int COL_WIDTH = 6;
+ public static final int COL_HEIGHT = 7;
+
+ static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, "
+ + VideoColumns._ID + " DESC";
+ static final String[] QUERY_PROJECTION = {
+ VideoColumns._ID, // 0, int
+ VideoColumns.TITLE, // 1, string
+ VideoColumns.MIME_TYPE, // 2, string
+ VideoColumns.DATE_TAKEN, // 3, int
+ VideoColumns.DATE_MODIFIED, // 4, int
+ VideoColumns.DATA, // 5, string
+ VideoColumns.WIDTH, // 6, int
+ VideoColumns.HEIGHT, // 7, int
+ VideoColumns.RESOLUTION
+ };
+
+ static Video buildFromCursor(Cursor c) {
+ Video d = new Video();
+ d.id = c.getLong(COL_ID);
+ d.title = c.getString(COL_TITLE);
+ d.mimeType = c.getString(COL_MIME_TYPE);
+ d.dateTaken = c.getLong(COL_DATE_TAKEN);
+ d.dateModified = c.getLong(COL_DATE_MODIFIED);
+ d.path = c.getString(COL_DATA);
+ d.width = c.getInt(COL_WIDTH);
+ d.height = c.getInt(COL_HEIGHT);
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(d.path);
+ String rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ if (d.width == 0 || d.height == 0) {
+ d.width = Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+ d.height = Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+ }
+ retriever.release();
+ if (rotation.equals("90") || rotation.equals("270")) {
+ int b = d.width;
+ d.width = d.height;
+ d.height = b;
+ }
+ return d;
+ }
+
+ @Override
+ public String toString() {
+ return "Video:" + ",data=" + path + ",mimeType=" + mimeType
+ + "," + width + "x" + height + ",date=" + new Date(dateTaken);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PHOTO;
+ }
+
+ @Override
+ protected BitmapLoadTask getBitmapLoadTask(
+ ImageView v, int decodeWidth, int decodeHeight) {
+ return new VideoBitmapLoadTask(v);
+ }
+
+ private final class VideoBitmapLoadTask extends BitmapLoadTask {
+
+ public VideoBitmapLoadTask(ImageView v) {
+ super(v);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... v) {
+ if (isCancelled() || !isUsing()) {
+ return null;
+ }
+ android.media.MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(path);
+ byte[] data = retriever.getEmbeddedPicture();
+ Bitmap bitmap = null;
+ if (isCancelled() || !isUsing()) {
+ retriever.release();
+ return null;
+ }
+ if (data != null) {
+ bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+ if (bitmap == null) {
+ bitmap = (Bitmap) retriever.getFrameAtTime();
+ }
+ retriever.release();
+ return bitmap;
+ }
+ }
+ }
+}
+
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index 9aeb96ffd..c836c9168 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -80,6 +80,13 @@ public class FilmStripView extends ViewGroup {
public int getHeight();
public int getType();
public boolean isActionSupported(int action);
+
+ // prepare() should be called first time before using it.
+ public void prepare();
+
+ // recycle() should be called before we nullify the reference to this
+ // data.
+ public void recycle();
}
public interface DataAdapter {
@@ -102,7 +109,7 @@ public class FilmStripView extends ViewGroup {
public int getTotalNumber();
public View getView(Context context, int id);
public ImageData getImageData(int id);
- public void suggestSize(int w, int h);
+ public void suggestDecodeSize(int w, int h);
public void setListener(Listener listener);
}
@@ -232,7 +239,7 @@ public class FilmStripView extends ViewGroup {
int boundWidth = MeasureSpec.getSize(widthMeasureSpec);
int boundHeight = MeasureSpec.getSize(heightMeasureSpec);
if (mDataAdapter != null) {
- mDataAdapter.suggestSize(boundWidth / 2, boundHeight / 2);
+ mDataAdapter.suggestDecodeSize(boundWidth / 2, boundHeight / 2);
}
int wMode = View.MeasureSpec.EXACTLY;
@@ -290,6 +297,9 @@ public class FilmStripView extends ViewGroup {
}
private ViewInfo buildInfoFromData(int dataID) {
+ ImageData data = mDataAdapter.getImageData(dataID);
+ if (data == null) return null;
+ data.prepare();
View v = mDataAdapter.getView(mContext, dataID);
if (v == null) return null;
v.setPadding(H_PADDING, 0, H_PADDING, 0);
@@ -298,6 +308,14 @@ public class FilmStripView extends ViewGroup {
return info;
}
+ private void removeInfo(int infoID) {
+ if (infoID >= mViewInfo.length || mViewInfo[infoID] == null) return;
+
+ removeView(mViewInfo[infoID].getView());
+ mDataAdapter.getImageData(mViewInfo[infoID].getID()).recycle();
+ mViewInfo[infoID] = null;
+ }
+
// We try to keep the one closest to the center of the screen at position mCurrentInfo.
private void stepIfNeeded() {
int nearest = findTheNearestView(mCenterPosition);
@@ -307,36 +325,34 @@ public class FilmStripView extends ViewGroup {
int adjust = nearest - mCurrentInfo;
if (adjust > 0) {
for (int k = 0; k < adjust; k++) {
- if (mViewInfo[k] != null) {
- removeView(mViewInfo[k].getView());
- }
+ removeInfo(k);
}
for (int k = 0; k + adjust < BUFFER_SIZE; k++) {
mViewInfo[k] = mViewInfo[k + adjust];
}
for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
mViewInfo[k] = null;
- if (mViewInfo[k - 1] != null)
+ if (mViewInfo[k - 1] != null) {
mViewInfo[k] = buildInfoFromData(mViewInfo[k - 1].getID() + 1);
+ }
}
} else {
for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
- if (mViewInfo[k] != null) {
- removeView(mViewInfo[k].getView());
- }
+ removeInfo(k);
}
for (int k = BUFFER_SIZE - 1; k + adjust >= 0; k--) {
mViewInfo[k] = mViewInfo[k + adjust];
}
for (int k = -1 - adjust; k >= 0; k--) {
mViewInfo[k] = null;
- if (mViewInfo[k + 1] != null)
+ if (mViewInfo[k + 1] != null) {
mViewInfo[k] = buildInfoFromData(mViewInfo[k + 1].getID() - 1);
+ }
}
}
}
- // Don't go out of bound.
+ // Don't go beyond the bound.
private void adjustCenterPosition() {
ViewInfo curr = mViewInfo[mCurrentInfo];
if (curr == null) return;
@@ -416,7 +432,7 @@ public class FilmStripView extends ViewGroup {
public void setDataAdapter(DataAdapter adapter) {
mDataAdapter = adapter;
- mDataAdapter.suggestSize(getMeasuredWidth(), getMeasuredHeight());
+ mDataAdapter.suggestDecodeSize(getMeasuredWidth(), getMeasuredHeight());
mDataAdapter.setListener(new DataAdapter.Listener() {
@Override
public void onDataLoaded() {