From bd26069d391830856c57c2141cd2efbc8423d871 Mon Sep 17 00:00:00 2001 From: Angus Kong Date: Wed, 7 Aug 2013 14:52:56 -0700 Subject: Refactor data/LocalData. Make the design more easily understood. Change-Id: I4f7dbe7d3f0a0534c13996b773f1767997848746 --- src/com/android/camera/CameraActivity.java | 3 +- src/com/android/camera/data/CameraDataAdapter.java | 32 +- src/com/android/camera/data/CameraPreviewData.java | 2 +- src/com/android/camera/data/LocalData.java | 697 +-------------------- src/com/android/camera/data/LocalMediaData.java | 600 ++++++++++++++++++ src/com/android/camera/data/SimpleViewData.java | 143 +++++ 6 files changed, 764 insertions(+), 713 deletions(-) create mode 100644 src/com/android/camera/data/LocalMediaData.java create mode 100644 src/com/android/camera/data/SimpleViewData.java diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index 57da90e60..508dfea34 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -49,6 +49,7 @@ import com.android.camera.data.FixedFirstDataAdapter; import com.android.camera.data.FixedLastDataAdapter; import com.android.camera.data.LocalData; import com.android.camera.data.LocalDataAdapter; +import com.android.camera.data.SimpleViewData; import com.android.camera.support.common.ApiHelper; import com.android.camera.ui.CameraSwitcher; import com.android.camera.ui.CameraSwitcher.CameraSwitchListener; @@ -388,7 +389,7 @@ public class CameraActivity extends Activity R.layout.secure_album_placeholder, null); mDataAdapter = new FixedLastDataAdapter( mWrappedDataAdapter, - new LocalData.LocalViewData( + new SimpleViewData( v, v.getDrawable().getIntrinsicWidth(), v.getDrawable().getIntrinsicHeight(), diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java index aaf5ebe59..b125bdc2e 100644 --- a/src/com/android/camera/data/CameraDataAdapter.java +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -146,14 +146,14 @@ public class CameraDataAdapter implements LocalDataAdapter { @Override public void addNewVideo(ContentResolver cr, Uri uri) { Cursor c = cr.query(uri, - LocalData.Video.QUERY_PROJECTION, + LocalMediaData.VideoData.QUERY_PROJECTION, MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, - LocalData.Video.QUERY_ORDER); + LocalMediaData.VideoData.QUERY_ORDER); if (c == null || !c.moveToFirst()) { return; } int pos = findDataByContentUri(uri); - LocalData.Video newData = LocalData.Video.buildFromCursor(c); + LocalMediaData.VideoData newData = LocalMediaData.VideoData.buildFromCursor(c); if (pos != -1) { // A duplicate one, just do a substitute. updateData(pos, newData); @@ -167,14 +167,14 @@ public class CameraDataAdapter implements LocalDataAdapter { @Override public void addNewPhoto(ContentResolver cr, Uri uri) { Cursor c = cr.query(uri, - LocalData.Photo.QUERY_PROJECTION, + LocalMediaData.PhotoData.QUERY_PROJECTION, MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, - LocalData.Photo.QUERY_ORDER); + LocalMediaData.PhotoData.QUERY_ORDER); if (c == null || !c.moveToFirst()) { return; } int pos = findDataByContentUri(uri); - LocalData.Photo newData = LocalData.Photo.buildFromCursor(c); + LocalMediaData.PhotoData newData = LocalMediaData.PhotoData.buildFromCursor(c); if (pos != -1) { // a duplicate one, just do a substitute. Log.v(TAG, "found duplicate photo"); @@ -288,19 +288,19 @@ public class CameraDataAdapter implements LocalDataAdapter { List l = new ArrayList(); // Photos Cursor c = resolver[0].query( - LocalData.Photo.CONTENT_URI, - LocalData.Photo.QUERY_PROJECTION, + LocalMediaData.PhotoData.CONTENT_URI, + LocalMediaData.PhotoData.QUERY_PROJECTION, MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, - LocalData.Photo.QUERY_ORDER); + LocalMediaData.PhotoData.QUERY_ORDER); if (c != null && c.moveToFirst()) { // build up the list. while (true) { - LocalData data = LocalData.Photo.buildFromCursor(c); + LocalData data = LocalMediaData.PhotoData.buildFromCursor(c); if (data != null) { l.add(data); } else { Log.e(TAG, "Error loading data:" - + c.getString(LocalData.Photo.COL_DATA)); + + c.getString(LocalMediaData.PhotoData.COL_DATA)); } if (c.isLast()) { break; @@ -313,20 +313,20 @@ public class CameraDataAdapter implements LocalDataAdapter { } c = resolver[0].query( - LocalData.Video.CONTENT_URI, - LocalData.Video.QUERY_PROJECTION, + LocalMediaData.VideoData.CONTENT_URI, + LocalMediaData.VideoData.QUERY_PROJECTION, MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH, - LocalData.Video.QUERY_ORDER); + LocalMediaData.VideoData.QUERY_ORDER); if (c != null && c.moveToFirst()) { // build up the list. c.moveToFirst(); while (true) { - LocalData data = LocalData.Video.buildFromCursor(c); + LocalData data = LocalMediaData.VideoData.buildFromCursor(c); if (data != null) { l.add(data); } else { Log.e(TAG, "Error loading data:" - + c.getString(LocalData.Video.COL_DATA)); + + c.getString(LocalMediaData.VideoData.COL_DATA)); } if (!c.isLast()) { c.moveToNext(); diff --git a/src/com/android/camera/data/CameraPreviewData.java b/src/com/android/camera/data/CameraPreviewData.java index 8f8e2138d..a34f5787b 100644 --- a/src/com/android/camera/data/CameraPreviewData.java +++ b/src/com/android/camera/data/CameraPreviewData.java @@ -23,7 +23,7 @@ import com.android.camera.ui.FilmStripView.ImageData; /** * A class implementing {@link LocalData} to represent a camera preview. */ -public class CameraPreviewData extends LocalData.LocalViewData { +public class CameraPreviewData extends SimpleViewData { private boolean mPreviewLocked; diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java index 10cf9aec6..f71abaa0c 100644 --- a/src/com/android/camera/data/LocalData.java +++ b/src/com/android/camera/data/LocalData.java @@ -16,38 +16,16 @@ package com.android.camera.data; -import java.io.File; -import java.util.Comparator; -import java.util.Date; - 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.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.media.MediaMetadataRetriever; import android.net.Uri; -import android.os.AsyncTask; -import android.provider.MediaStore; -import android.provider.MediaStore.Images.ImageColumns; -import android.provider.MediaStore.Video.VideoColumns; -import android.util.Log; -import android.view.Gravity; import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.android.camera.Util; -import com.android.camera.data.PanoramaMetadataLoader.PanoramaMetadataCallback; import com.android.camera.ui.FilmStripView; -import com.android.camera.util.PhotoSphereHelper.PanoramaMetadata; import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; -import com.android.camera2.R; + +import java.util.Comparator; /** * An abstract interface that represents the local media data. Also implements @@ -147,676 +125,5 @@ public interface LocalData extends FilmStripView.ImageData { } } - // Implementations below. - - /** - * 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 sub-classing 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; - - /** The panorama metadata information of this media data. */ - private PanoramaMetadata mPanoramaMetadata; - - /** Used to load photo sphere metadata from image files. */ - private PanoramaMetadataLoader mPanoramaMetadataLoader = null; - - // 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 String getPath() { - return path; - } - - @Override - public boolean isUIActionSupported(int action) { - return false; - } - - @Override - public boolean isDataActionSupported(int action) { - return false; - } - - @Override - public boolean delete(Context ctx) { - File f = new File(path); - return f.delete(); - } - - @Override - public void viewPhotoSphere(PanoramaViewHelper helper) { - helper.showPanorama(getContentUri()); - } - - @Override - public void isPhotoSphere(Context context, final PanoramaSupportCallback callback) { - // If we already have metadata, use it. - if (mPanoramaMetadata != null) { - callback.panoramaInfoAvailable(mPanoramaMetadata.mUsePanoramaViewer, - mPanoramaMetadata.mIsPanorama360); - } - - // Otherwise prepare a loader, if we don't have one already. - if (mPanoramaMetadataLoader == null) { - mPanoramaMetadataLoader = new PanoramaMetadataLoader(getContentUri()); - } - - // Load the metadata asynchronously. - mPanoramaMetadataLoader.getPanoramaMetadata(context, new PanoramaMetadataCallback() { - @Override - public void onPanoramaMetadataLoaded(PanoramaMetadata metadata) { - // Store the metadata and remove the loader to free up space. - mPanoramaMetadata = metadata; - mPanoramaMetadataLoader = null; - callback.panoramaInfoAvailable(metadata.mUsePanoramaViewer, - metadata.mIsPanorama360); - } - }); - } - - @Override - public void onFullScreen(boolean fullScreen) { - // do nothing. - } - - @Override - public boolean canSwipeInFullScreen() { - return true; - } - - protected ImageView fillImageView(Context ctx, ImageView v, - int decodeWidth, int decodeHeight, Drawable placeHolder) { - v.setScaleType(ImageView.ScaleType.FIT_XY); - v.setImageDrawable(placeHolder); - - BitmapLoadTask task = getBitmapLoadTask(v, decodeWidth, decodeHeight); - task.execute(); - return v; - } - - @Override - public View getView(Context ctx, - int decodeWidth, int decodeHeight, Drawable placeHolder) { - return fillImageView(ctx, new ImageView(ctx), - decodeWidth, decodeHeight, placeHolder); - } - - @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 { - 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; - } - BitmapDrawable d = new BitmapDrawable(bitmap); - mView.setScaleType(ImageView.ScaleType.FIT_XY); - mView.setImageDrawable(d); - } - } - } - - 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 Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - - static final String QUERY_ORDER = ImageColumns.DATE_TAKEN + " DESC, " - + ImageColumns._ID + " DESC"; - /** - * These values should be kept in sync with column IDs (COL_*) above. - */ - 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 mSupportedUIActions = - FilmStripView.ImageData.ACTION_DEMOTE - | FilmStripView.ImageData.ACTION_PROMOTE; - private static final int mSupportedDataActions = - LocalData.ACTION_DELETE; - - /** 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.w(TAG, "Warning! zero dimension for " - + d.path + ":" + d.width + "x" + d.height); - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inJustDecodeBounds = true; - BitmapFactory.decodeFile(d.path, opts); - if (opts.outWidth != -1 && opts.outHeight != -1) { - d.width = opts.outWidth; - d.height = opts.outHeight; - } else { - Log.w(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 - public boolean isUIActionSupported(int action) { - return ((action & mSupportedUIActions) == action); - } - - @Override - public boolean isDataActionSupported(int action) { - return ((action & mSupportedDataActions) == action); - } - - @Override - public boolean delete(Context c) { - ContentResolver cr = c.getContentResolver(); - cr.delete(CONTENT_URI, ImageColumns._ID + "=" + id, null); - return super.delete(c); - } - - @Override - public Uri getContentUri() { - Uri baseUri = CONTENT_URI; - return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); - } - - @Override - public boolean refresh(ContentResolver resolver) { - Cursor c = resolver.query( - getContentUri(), QUERY_PROJECTION, null, null, null); - if (c == null || !c.moveToFirst()) { - return false; - } - Photo newData = buildFromCursor(c); - id = newData.id; - title = newData.title; - mimeType = newData.mimeType; - dateTaken = newData.dateTaken; - dateModified = newData.dateModified; - path = newData.path; - orientation = newData.orientation; - width = newData.width; - height = newData.height; - return true; - } - - @Override - protected BitmapLoadTask getBitmapLoadTask( - ImageView v, int decodeWidth, int decodeHeight) { - return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight); - } - - 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(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; - public static final int COL_RESOLUTION = 8; - - static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - - private static final int mSupportedUIActions = - FilmStripView.ImageData.ACTION_DEMOTE - | FilmStripView.ImageData.ACTION_PROMOTE; - private static final int mSupportedDataActions = - LocalData.ACTION_DELETE - | LocalData.ACTION_PLAY; - - static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, " - + VideoColumns._ID + " DESC"; - /** - * These values should be kept in sync with column IDs (COL_*) above. - */ - 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 // 8, string - }; - - private Uri mPlayUri; - - 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); - d.mPlayUri = d.getContentUri(); - 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 != null - && (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 - public boolean isUIActionSupported(int action) { - return ((action & mSupportedUIActions) == action); - } - - @Override - public boolean isDataActionSupported(int action) { - return ((action & mSupportedDataActions) == action); - } - - @Override - public boolean delete(Context ctx) { - ContentResolver cr = ctx.getContentResolver(); - cr.delete(CONTENT_URI, VideoColumns._ID + "=" + id, null); - return super.delete(ctx); - } - - @Override - public Uri getContentUri() { - Uri baseUri = CONTENT_URI; - return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); - } - - @Override - public boolean refresh(ContentResolver resolver) { - Cursor c = resolver.query( - getContentUri(), QUERY_PROJECTION, null, null, null); - if (c == null && !c.moveToFirst()) { - return false; - } - Video newData = buildFromCursor(c); - id = newData.id; - title = newData.title; - mimeType = newData.mimeType; - dateTaken = newData.dateTaken; - dateModified = newData.dateModified; - path = newData.path; - width = newData.width; - height = newData.height; - mPlayUri = newData.mPlayUri; - return true; - } - - @Override - public View getView(final Context ctx, - int decodeWidth, int decodeHeight, Drawable placeHolder) { - - // ImageView for the bitmap. - ImageView iv = new ImageView(ctx); - iv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); - fillImageView(ctx, iv, decodeWidth, decodeHeight, placeHolder); - - // ImageView for the play icon. - ImageView icon = new ImageView(ctx); - icon.setImageResource(R.drawable.ic_control_play); - icon.setScaleType(ImageView.ScaleType.CENTER); - icon.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); - icon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Util.playVideo(ctx, mPlayUri, title); - } - }); - - FrameLayout f = new FrameLayout(ctx); - f.addView(iv); - f.addView(icon); - return f; - } - - @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 = retriever.getFrameAtTime(); - } - retriever.release(); - return bitmap; - } - } - } - - /** - * A LocalData that does nothing but only shows a view. - */ - public static class LocalViewData implements LocalData { - private int mWidth; - private int mHeight; - private View mView; - private long mDateTaken; - private long mDateModified; - - public LocalViewData(View v, - int width, int height, - int dateTaken, int dateModified) { - mView = v; - mWidth = width; - mHeight = height; - mDateTaken = dateTaken; - mDateModified = dateModified; - } - - @Override - public long getDateTaken() { - return mDateTaken; - } - - @Override - public long getDateModified() { - return mDateModified; - } - - @Override - public String getTitle() { - return ""; - } - - @Override - public int getWidth() { - return mWidth; - } - - @Override - public int getHeight() { - return mHeight; - } - - @Override - public int getType() { - return FilmStripView.ImageData.TYPE_PHOTO; - } - - @Override - public String getPath() { - return ""; - } - - @Override - public Uri getContentUri() { - return Uri.EMPTY; - } - - @Override - public boolean refresh(ContentResolver resolver) { - return false; - } - - @Override - public boolean isUIActionSupported(int action) { - return false; - } - - @Override - public boolean isDataActionSupported(int action) { - return false; - } - - @Override - public boolean delete(Context c) { - return false; - } - - @Override - public View getView(Context c, int width, int height, Drawable placeHolder) { - return mView; - } - - @Override - public void prepare() { - // do nothing. - } - - @Override - public void recycle() { - // do nothing. - } - - @Override - public void isPhotoSphere(Context context, PanoramaSupportCallback callback) { - // Not a photo sphere panorama. - callback.panoramaInfoAvailable(false, false); - } - - @Override - public void viewPhotoSphere(PanoramaViewHelper helper) { - // do nothing. - } - - @Override - public void onFullScreen(boolean fullScreen) { - // do nothing. - } - - @Override - public boolean canSwipeInFullScreen() { - return true; - } - } } diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java new file mode 100644 index 000000000..d80862cad --- /dev/null +++ b/src/com/android/camera/data/LocalMediaData.java @@ -0,0 +1,600 @@ +/* + * 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.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.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.camera.Util; +import com.android.camera.ui.FilmStripView; +import com.android.camera.util.PhotoSphereHelper; +import com.android.camera2.R; + +import java.io.File; +import java.util.Date; + +/** + * 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 sub-classing BitmapLoadTask and overriding + * doInBackground() to return a bitmap. + */ +public abstract 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; + + /** The panorama metadata information of this media data. */ + private PhotoSphereHelper.PanoramaMetadata mPanoramaMetadata; + + /** Used to load photo sphere metadata from image files. */ + private PanoramaMetadataLoader mPanoramaMetadataLoader = null; + + // 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 String getPath() { + return path; + } + + @Override + public boolean isUIActionSupported(int action) { + return false; + } + + @Override + public boolean isDataActionSupported(int action) { + return false; + } + + @Override + public boolean delete(Context ctx) { + File f = new File(path); + return f.delete(); + } + + @Override + public void viewPhotoSphere(PhotoSphereHelper.PanoramaViewHelper helper) { + helper.showPanorama(getContentUri()); + } + + @Override + public void isPhotoSphere(Context context, final PanoramaSupportCallback callback) { + // If we already have metadata, use it. + if (mPanoramaMetadata != null) { + callback.panoramaInfoAvailable(mPanoramaMetadata.mUsePanoramaViewer, + mPanoramaMetadata.mIsPanorama360); + } + + // Otherwise prepare a loader, if we don't have one already. + if (mPanoramaMetadataLoader == null) { + mPanoramaMetadataLoader = new PanoramaMetadataLoader(getContentUri()); + } + + // Load the metadata asynchronously. + mPanoramaMetadataLoader.getPanoramaMetadata(context, new PanoramaMetadataLoader.PanoramaMetadataCallback() { + @Override + public void onPanoramaMetadataLoaded(PhotoSphereHelper.PanoramaMetadata metadata) { + // Store the metadata and remove the loader to free up space. + mPanoramaMetadata = metadata; + mPanoramaMetadataLoader = null; + callback.panoramaInfoAvailable(metadata.mUsePanoramaViewer, + metadata.mIsPanorama360); + } + }); + } + + @Override + public void onFullScreen(boolean fullScreen) { + // do nothing. + } + + @Override + public boolean canSwipeInFullScreen() { + return true; + } + + protected ImageView fillImageView(Context ctx, ImageView v, + int decodeWidth, int decodeHeight, Drawable placeHolder) { + v.setScaleType(ImageView.ScaleType.FIT_XY); + v.setImageDrawable(placeHolder); + + BitmapLoadTask task = getBitmapLoadTask(v, decodeWidth, decodeHeight); + task.execute(); + return v; + } + + @Override + public View getView(Context ctx, + int decodeWidth, int decodeHeight, Drawable placeHolder) { + return fillImageView(ctx, new ImageView(ctx), + decodeWidth, decodeHeight, placeHolder); + } + + @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); + + public static class PhotoData extends LocalMediaData { + private static final String TAG = "CAM_PhotoData"; + + 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 Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + static final String QUERY_ORDER = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC, " + + MediaStore.Images.ImageColumns._ID + " DESC"; + /** + * These values should be kept in sync with column IDs (COL_*) above. + */ + static final String[] QUERY_PROJECTION = { + MediaStore.Images.ImageColumns._ID, // 0, int + MediaStore.Images.ImageColumns.TITLE, // 1, string + MediaStore.Images.ImageColumns.MIME_TYPE, // 2, string + MediaStore.Images.ImageColumns.DATE_TAKEN, // 3, int + MediaStore.Images.ImageColumns.DATE_MODIFIED, // 4, int + MediaStore.Images.ImageColumns.DATA, // 5, string + MediaStore.Images.ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270 + MediaStore.Images.ImageColumns.WIDTH, // 7, int + MediaStore.Images.ImageColumns.HEIGHT, // 8, int + }; + + private static final int mSupportedUIActions = + FilmStripView.ImageData.ACTION_DEMOTE + | FilmStripView.ImageData.ACTION_PROMOTE; + private static final int mSupportedDataActions = + LocalData.ACTION_DELETE; + + /** 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 PhotoData buildFromCursor(Cursor c) { + PhotoData d = new PhotoData(); + 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.w(TAG, "Warning! zero dimension for " + + d.path + ":" + d.width + "x" + d.height); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(d.path, opts); + if (opts.outWidth != -1 && opts.outHeight != -1) { + d.width = opts.outWidth; + d.height = opts.outHeight; + } else { + Log.w(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 + public boolean isUIActionSupported(int action) { + return ((action & mSupportedUIActions) == action); + } + + @Override + public boolean isDataActionSupported(int action) { + return ((action & mSupportedDataActions) == action); + } + + @Override + public boolean delete(Context c) { + ContentResolver cr = c.getContentResolver(); + cr.delete(CONTENT_URI, MediaStore.Images.ImageColumns._ID + "=" + id, null); + return super.delete(c); + } + + @Override + public Uri getContentUri() { + Uri baseUri = CONTENT_URI; + return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); + } + + @Override + public boolean refresh(ContentResolver resolver) { + Cursor c = resolver.query( + getContentUri(), QUERY_PROJECTION, null, null, null); + if (c == null || !c.moveToFirst()) { + return false; + } + PhotoData newData = buildFromCursor(c); + id = newData.id; + title = newData.title; + mimeType = newData.mimeType; + dateTaken = newData.dateTaken; + dateModified = newData.dateModified; + path = newData.path; + orientation = newData.orientation; + width = newData.width; + height = newData.height; + return true; + } + + @Override + protected BitmapLoadTask getBitmapLoadTask( + ImageView v, int decodeWidth, int decodeHeight) { + return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight); + } + + 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(orientation); + b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false); + } + return b; + } + } + } + + public static class VideoData 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; + public static final int COL_RESOLUTION = 8; + + static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + + private static final int mSupportedUIActions = + FilmStripView.ImageData.ACTION_DEMOTE + | FilmStripView.ImageData.ACTION_PROMOTE; + private static final int mSupportedDataActions = + LocalData.ACTION_DELETE + | LocalData.ACTION_PLAY; + + static final String QUERY_ORDER = MediaStore.Video.VideoColumns.DATE_TAKEN + " DESC, " + + MediaStore.Video.VideoColumns._ID + " DESC"; + /** + * These values should be kept in sync with column IDs (COL_*) above. + */ + static final String[] QUERY_PROJECTION = { + MediaStore.Video.VideoColumns._ID, // 0, int + MediaStore.Video.VideoColumns.TITLE, // 1, string + MediaStore.Video.VideoColumns.MIME_TYPE, // 2, string + MediaStore.Video.VideoColumns.DATE_TAKEN, // 3, int + MediaStore.Video.VideoColumns.DATE_MODIFIED, // 4, int + MediaStore.Video.VideoColumns.DATA, // 5, string + MediaStore.Video.VideoColumns.WIDTH, // 6, int + MediaStore.Video.VideoColumns.HEIGHT, // 7, int + MediaStore.Video.VideoColumns.RESOLUTION // 8, string + }; + + private Uri mPlayUri; + + static VideoData buildFromCursor(Cursor c) { + VideoData d = new VideoData(); + 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); + d.mPlayUri = d.getContentUri(); + 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 != null + && (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 + public boolean isUIActionSupported(int action) { + return ((action & mSupportedUIActions) == action); + } + + @Override + public boolean isDataActionSupported(int action) { + return ((action & mSupportedDataActions) == action); + } + + @Override + public boolean delete(Context ctx) { + ContentResolver cr = ctx.getContentResolver(); + cr.delete(CONTENT_URI, MediaStore.Video.VideoColumns._ID + "=" + id, null); + return super.delete(ctx); + } + + @Override + public Uri getContentUri() { + Uri baseUri = CONTENT_URI; + return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); + } + + @Override + public boolean refresh(ContentResolver resolver) { + Cursor c = resolver.query( + getContentUri(), QUERY_PROJECTION, null, null, null); + if (c == null && !c.moveToFirst()) { + return false; + } + VideoData newData = buildFromCursor(c); + id = newData.id; + title = newData.title; + mimeType = newData.mimeType; + dateTaken = newData.dateTaken; + dateModified = newData.dateModified; + path = newData.path; + width = newData.width; + height = newData.height; + mPlayUri = newData.mPlayUri; + return true; + } + + @Override + public View getView(final Context ctx, + int decodeWidth, int decodeHeight, Drawable placeHolder) { + + // ImageView for the bitmap. + ImageView iv = new ImageView(ctx); + iv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); + fillImageView(ctx, iv, decodeWidth, decodeHeight, placeHolder); + + // ImageView for the play icon. + ImageView icon = new ImageView(ctx); + icon.setImageResource(R.drawable.ic_control_play); + icon.setScaleType(ImageView.ScaleType.CENTER); + icon.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + icon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Util.playVideo(ctx, mPlayUri, title); + } + }); + + FrameLayout f = new FrameLayout(ctx); + f.addView(iv); + f.addView(icon); + return f; + } + + @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; + } + 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 = retriever.getFrameAtTime(); + } + retriever.release(); + return bitmap; + } + } + } + + /** + * An {@link AsyncTask} class that loads the bitmap in the background thread. + * Sub-classes should implement their own + * {@code BitmapLoadTask#doInBackground(Void...)}." + */ + protected abstract class BitmapLoadTask extends AsyncTask { + 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; + } + BitmapDrawable d = new BitmapDrawable(bitmap); + mView.setScaleType(ImageView.ScaleType.FIT_XY); + mView.setImageDrawable(d); + } + } +} diff --git a/src/com/android/camera/data/SimpleViewData.java b/src/com/android/camera/data/SimpleViewData.java new file mode 100644 index 000000000..4d64c5360 --- /dev/null +++ b/src/com/android/camera/data/SimpleViewData.java @@ -0,0 +1,143 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.view.View; + +import com.android.camera.ui.FilmStripView; +import com.android.camera.util.PhotoSphereHelper; + +/** + * A LocalData that does nothing but only shows a view. + */ +public class SimpleViewData implements LocalData { + private int mWidth; + private int mHeight; + private View mView; + private long mDateTaken; + private long mDateModified; + + public SimpleViewData( + View v, int width, int height, + int dateTaken, int dateModified) { + mView = v; + mWidth = width; + mHeight = height; + mDateTaken = dateTaken; + mDateModified = dateModified; + } + + @Override + public long getDateTaken() { + return mDateTaken; + } + + @Override + public long getDateModified() { + return mDateModified; + } + + @Override + public String getTitle() { + return ""; + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public int getType() { + return FilmStripView.ImageData.TYPE_PHOTO; + } + + @Override + public String getPath() { + return ""; + } + + @Override + public Uri getContentUri() { + return Uri.EMPTY; + } + + @Override + public boolean refresh(ContentResolver resolver) { + return false; + } + + @Override + public boolean isUIActionSupported(int action) { + return false; + } + + @Override + public boolean isDataActionSupported(int action) { + return false; + } + + @Override + public boolean delete(Context c) { + return false; + } + + @Override + public View getView(Context c, int width, int height, Drawable placeHolder) { + return mView; + } + + @Override + public void prepare() { + // do nothing. + } + + @Override + public void recycle() { + // do nothing. + } + + @Override + public void isPhotoSphere(Context context, PanoramaSupportCallback callback) { + // Not a photo sphere panorama. + callback.panoramaInfoAvailable(false, false); + } + + @Override + public void viewPhotoSphere(PhotoSphereHelper.PanoramaViewHelper helper) { + // do nothing. + } + + @Override + public void onFullScreen(boolean fullScreen) { + // do nothing. + } + + @Override + public boolean canSwipeInFullScreen() { + return true; + } +} -- cgit v1.2.3