diff options
author | Angus Kong <shkong@google.com> | 2013-05-06 10:42:28 -0700 |
---|---|---|
committer | Angus Kong <shkong@google.com> | 2013-05-14 14:17:56 -0700 |
commit | 750e8ec8af168afd318d47082b326b95f1cca517 (patch) | |
tree | 7dce9ff00573ec904bfb3fb9c5c57aed7161ca55 /src | |
parent | 49b9ba2ba89760f297ecc7d6d94d68fb4b836be1 (diff) | |
download | android_packages_apps_Snap-750e8ec8af168afd318d47082b326b95f1cca517.tar.gz android_packages_apps_Snap-750e8ec8af168afd318d47082b326b95f1cca517.tar.bz2 android_packages_apps_Snap-750e8ec8af168afd318d47082b326b95f1cca517.zip |
Improve bitmap load efficiency.
Call prepare before/recycle after using image data.
Change-Id: I3387c8ca68f57c3949fed2aaa3e26490e66c791a
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/camera/data/CameraDataAdapter.java | 419 | ||||
-rw-r--r-- | src/com/android/camera/data/LocalData.java | 421 | ||||
-rw-r--r-- | src/com/android/camera/ui/FilmStripView.java | 40 |
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() { |