diff options
author | Wu-cheng Li <wuchengli@google.com> | 2010-09-17 15:48:59 -0700 |
---|---|---|
committer | Wu-cheng Li <wuchengli@google.com> | 2010-09-28 15:11:45 -0700 |
commit | 9f73bd9a85d295091fae39dc256a122e1843e2e8 (patch) | |
tree | 61468fe0a61b24ec6cd3856629b2b87fedaf9efe | |
parent | 6bf9e407d0dc0cd7e2f1f4799987523b107e8c96 (diff) | |
download | LegacyCamera-9f73bd9a85d295091fae39dc256a122e1843e2e8.zip LegacyCamera-9f73bd9a85d295091fae39dc256a122e1843e2e8.tar.gz LegacyCamera-9f73bd9a85d295091fae39dc256a122e1843e2e8.tar.bz2 |
Add last captured image thumbnails for xlarge devices.
Change-Id: I96d5472b62a7ffcc57642c09a0a7567a19f6ed42
-rw-r--r-- | res/layout-xlarge/camera_control.xml | 27 | ||||
-rw-r--r-- | res/layout/thumbnail_item.xml | 21 | ||||
-rw-r--r-- | src/com/android/camera/Camera.java | 173 | ||||
-rw-r--r-- | src/com/android/camera/RotateImageView.java | 147 | ||||
-rw-r--r-- | src/com/android/camera/ThumbnailAdapter.java | 77 | ||||
-rw-r--r-- | src/com/android/camera/ThumbnailController.java | 195 | ||||
-rw-r--r-- | src/com/android/camera/VideoCamera.java | 166 |
7 files changed, 476 insertions, 330 deletions
diff --git a/res/layout-xlarge/camera_control.xml b/res/layout-xlarge/camera_control.xml index e1ca708..497da74 100644 --- a/res/layout-xlarge/camera_control.xml +++ b/res/layout-xlarge/camera_control.xml @@ -16,28 +16,30 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/control_bar" - android:orientation="vertical" android:layout_height="match_parent" - android:layout_width="90dp" + android:layout_width="190dp" android:layout_marginTop="20dp" android:layout_marginBottom="20dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_alignParentRight="true"> - <com.android.camera.RotateImageView - android:id="@+id/review_thumbnail" + <ListView android:id="@+id/image_list" + android:layout_width="93dp" + android:layout_height="match_parent" android:layout_alignParentTop="true" - android:layout_centerHorizontal="true" - android:layout_height="70dp" - android:layout_width="70dp" - android:clickable="true" - android:focusable="false" - android:background="@drawable/border_last_picture"/> + android:layout_alignParentBottom="true" + android:layout_marginLeft="10dp" + android:layout_marginRight="10dp" + android:dividerHeight="10dp" + android:orientation="vertical" + android:gravity="center"> + </ListView> <com.android.camera.ShutterButton android:id="@+id/shutter_button" android:layout_centerInParent="true" - android:layout_centerHorizontal="true" + android:layout_alignParentRight="true" + android:layout_toRightOf="@id/image_list" android:layout_height="wrap_content" android:layout_width="wrap_content" android:scaleType="center" @@ -50,7 +52,8 @@ android:orientation="vertical" android:gravity="center" android:layout_alignParentBottom="true" - android:layout_centerHorizontal="true" + android:layout_alignParentRight="true" + android:layout_toRightOf="@id/image_list" android:layout_height="wrap_content" android:layout_width="wrap_content"> <com.android.camera.RotateImageView android:id="@+id/video_switch_icon" diff --git a/res/layout/thumbnail_item.xml b/res/layout/thumbnail_item.xml new file mode 100644 index 0000000..7f6522c --- /dev/null +++ b/res/layout/thumbnail_item.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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. +--> +<com.android.camera.RotateImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_alignParentTop="true" + android:layout_alignParentRight="true" + android:layout_height="90dp" + android:layout_width="90dp" + android:background="@drawable/border_last_picture"/> diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index 44685dc..278a9b3 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -24,10 +24,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Camera.CameraInfo; @@ -43,23 +43,22 @@ import android.media.ToneGenerator; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Debug; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; -import android.os.SystemClock; import android.provider.MediaStore; +import android.provider.MediaStore.Images.ImageColumns; import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; -import android.view.Display; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.SurfaceHolder; @@ -68,9 +67,11 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.view.MenuItem.OnMenuItemClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.CursorAdapter; import android.widget.FrameLayout; -import android.widget.ImageView; +import android.widget.ListView; import com.android.camera.gallery.IImage; import com.android.camera.gallery.IImageList; @@ -163,10 +164,14 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, private GLRootView mGLRootView; - // mPostCaptureAlert, mLastPictureButton, mThumbController - // are non-null only if isImageCaptureIntent() is true. - private ImageView mLastPictureButton; - private ThumbnailController mThumbController; + // The layouts of small devices have a thumbnail button, which shows the last + // captured picture. + private RotateImageView mThumbnailButton; + // The layouts of xlarge devices have a list of thumbnails, which show the + // last captured pictures. + private ListView mThumbnailList; + private OnItemClickListener mThumbnailItemClickListener = + new ThumbnailItemClickListener(); // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true. private String mCropValue; @@ -323,14 +328,8 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, mContentResolver = getContentResolver(); if (!mIsImageCaptureIntent) { findViewById(R.id.camera_switch).setOnClickListener(this); - mLastPictureButton = - (ImageView) findViewById(R.id.review_thumbnail); - mLastPictureButton.setOnClickListener(this); - mThumbController = new ThumbnailController( - getResources(), mLastPictureButton, mContentResolver); - mThumbController.loadData(ImageManager.getLastImageThumbPath()); - // Update last image thumbnail. - updateThumbnailButton(); + initThumbnailButton(); + initThumbnailList(); } // Initialize shutter button. @@ -363,12 +362,82 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, }); } + private void initThumbnailButton() { + mThumbnailButton = + (RotateImageView) findViewById(R.id.review_thumbnail); + if (mThumbnailButton != null) { + mThumbnailButton.setOnClickListener(this); + mThumbnailButton.loadData(ImageManager.getLastImageThumbPath()); + updateThumbnailButton(); + } + } + private void updateThumbnailButton() { + if (mThumbnailButton == null) return; // Update last image if URI is invalid and the storage is ready. - if (!mThumbController.isUriValid() && mPicturesRemaining >= 0) { - updateLastImage(); + if (!mThumbnailButton.isUriValid() && mPicturesRemaining >= 0) { + IImageList list = ImageManager.makeImageList( + mContentResolver, + dataLocation(), + ImageManager.INCLUDE_IMAGES, + ImageManager.SORT_ASCENDING, + ImageManager.CAMERA_IMAGE_BUCKET_ID); + int count = list.getCount(); + if (count > 0) { + IImage image = list.getImageAt(count - 1); + Uri uri = image.fullSizeImageUri(); + mThumbnailButton.setData(uri, image.miniThumbBitmap()); + } else { + mThumbnailButton.setData(null, null); + } + list.close(); } - mThumbController.updateDisplayIfNeeded(); + } + + private void setLastPictureThumb(byte[] data, int degree, Uri uri) { + if (mThumbnailButton == null) return; + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = 16; + Bitmap lastPictureThumb = + BitmapFactory.decodeByteArray(data, 0, data.length, options); + lastPictureThumb = Util.rotate(lastPictureThumb, degree); + mThumbnailButton.setData(uri, lastPictureThumb); + } + + private void initThumbnailList() { + mThumbnailList = (ListView) findViewById(R.id.image_list); + if (mThumbnailList != null) { + int width = mThumbnailList.getWidth(); + int height = mThumbnailList.getHeight(); + int thumbnailCount = (height + mThumbnailList.getDividerHeight()) + / (width + mThumbnailList.getDividerHeight()); + Cursor cursor = getThumbnailsCursor(thumbnailCount); + ThumbnailAdapter adapter = new ThumbnailAdapter( + getApplicationContext(), R.layout.thumbnail_item, cursor, + true); + mThumbnailList.setAdapter(adapter); + mThumbnailList.setOnItemClickListener(mThumbnailItemClickListener); + } + } + + private void updateThumbnailList() { + if (mThumbnailList == null) return; + CursorAdapter adapter = (CursorAdapter) mThumbnailList.getAdapter(); + Cursor cursor = adapter.getCursor(); + cursor.requery(); + adapter.notifyDataSetChanged(); + } + + private Cursor getThumbnailsCursor(int thumbnailCount) { + Log.v(TAG, "thumbnailCount=" + thumbnailCount); + String[] projections = { MediaStore.Images.Thumbnails._ID }; + Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + .buildUpon() + .appendQueryParameter("limit", String.valueOf(thumbnailCount)) + .build(); + // TODO: managedQuery is deprecated. + return managedQuery(uri, projections, null, null, + ImageColumns._ID + " DESC"); } // If the activity is paused and resumed, this method will be called in @@ -486,6 +555,7 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, checkStorage(); if (!mIsImageCaptureIntent) { updateThumbnailButton(); + updateThumbnailList(); } } } @@ -747,7 +817,7 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, "com.android.camera.NEW_PICTURE", mLastContentUri)); setLastPictureThumb(data, degree, mImageCapture.getLastCaptureUri()); - mThumbController.updateDisplayIfNeeded(); + updateThumbnailList(); } else { mCaptureOnlyData = data; showPostCaptureAlert(); @@ -863,15 +933,6 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, return true; } - private void setLastPictureThumb(byte[] data, int degree, Uri uri) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = 16; - Bitmap lastPictureThumb = - BitmapFactory.decodeByteArray(data, 0, data.length, options); - lastPictureThumb = Util.rotate(lastPictureThumb, degree); - mThumbController.setData(uri, lastPictureThumb); - } - private String createName(long dateTaken) { Date date = new Date(dateTaken); SimpleDateFormat dateFormat = new SimpleDateFormat( @@ -1058,8 +1119,10 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, } private void setOrientationIndicator(int degree) { - ((RotateImageView) findViewById( - R.id.review_thumbnail)).setDegree(degree); + RotateImageView thumbnail = (RotateImageView) findViewById( + R.id.review_thumbnail); + if (thumbnail != null) thumbnail.setDegree(degree); + ((RotateImageView) findViewById( R.id.camera_switch_icon)).setDegree(degree); ((RotateImageView) findViewById( @@ -1096,7 +1159,7 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, break; case R.id.review_thumbnail: if (isCameraIdle()) { - viewLastImage(); + viewImage(mThumbnailButton); } break; case R.id.btn_done: @@ -1107,6 +1170,12 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, } } + private class ThumbnailItemClickListener implements OnItemClickListener { + public void onItemClick(AdapterView<?> p, View v, int pos, long id) { + viewImage((RotateImageView)v); + } + } + private Bitmap createCaptureBitmap(byte[] data) { // This is really stupid...we just want to read the orientation in // the jpeg header. @@ -1341,8 +1410,10 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, if (mFirstTimeInitialized) { mOrientationListener.disable(); if (!mIsImageCaptureIntent) { - mThumbController.storeData( - ImageManager.getLastImageThumbPath()); + if (mThumbnailButton != null) { + mThumbnailButton.storeData( + ImageManager.getLastImageThumbPath()); + } } hidePostCaptureAlert(); } @@ -1605,24 +1676,6 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, } } - private void updateLastImage() { - IImageList list = ImageManager.makeImageList( - mContentResolver, - dataLocation(), - ImageManager.INCLUDE_IMAGES, - ImageManager.SORT_ASCENDING, - ImageManager.CAMERA_IMAGE_BUCKET_ID); - int count = list.getCount(); - if (count > 0) { - IImage image = list.getImageAt(count - 1); - Uri uri = image.fullSizeImageUri(); - mThumbController.setData(uri, image.miniThumbBitmap()); - } else { - mThumbController.setData(null, null); - } - list.close(); - } - private void showCameraErrorAndFinish() { Resources ress = getResources(); Util.showFatalErrorAndFinish(Camera.this, @@ -1893,21 +1946,21 @@ public class Camera extends NoSearchActivity implements View.OnClickListener, MenuHelper.gotoCameraImageGallery(this); } - private void viewLastImage() { - if (mThumbController.isUriValid()) { - Intent intent = new Intent(Util.REVIEW_ACTION, mThumbController.getUri()); + private void viewImage(RotateImageView view) { + if(view.isUriValid()) { + Intent intent = new Intent(Util.REVIEW_ACTION, view.getUri()); try { startActivity(intent); } catch (ActivityNotFoundException ex) { try { - intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri()); + intent = new Intent(Intent.ACTION_VIEW, view.getUri()); startActivity(intent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "review image fail", e); + Log.e(TAG, "review image fail. uri=" + view.getUri(), e); } } } else { - Log.e(TAG, "Can't view last image."); + Log.e(TAG, "Uri invalid. uri=" + view.getUri()); } } diff --git a/src/com/android/camera/RotateImageView.java b/src/com/android/camera/RotateImageView.java index 40fd007..57a7f13 100644 --- a/src/com/android/camera/RotateImageView.java +++ b/src/com/android/camera/RotateImageView.java @@ -17,13 +17,30 @@ package com.android.camera; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Rect; +import android.graphics.drawable.TransitionDrawable; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.Rect; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.util.AttributeSet; +import android.util.Log; import android.view.animation.AnimationUtils; +import android.view.ViewGroup.LayoutParams; import android.widget.ImageView; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + /** * A @{code ImageView} which can rotate it's content. */ @@ -38,15 +55,21 @@ public class RotateImageView extends ImageView { private int mStartDegree = 0; private int mTargetDegree = 0; - private boolean mClockwise = false; + private boolean mClockwise = false, mEnableAnimation = true; private long mAnimationStartTime = 0; private long mAnimationEndTime = 0; + private Uri mUri; + public RotateImageView(Context context, AttributeSet attrs) { super(context, attrs); } + public void enableAnimation(boolean enable) { + mEnableAnimation = enable; + } + public void setDegree(int degree) { // make sure in the range of [0, 359] degree = degree >= 0 ? degree % 360 : degree % 360 + 360; @@ -72,7 +95,6 @@ public class RotateImageView extends ImageView { @Override protected void onDraw(Canvas canvas) { - Drawable drawable = getDrawable(); if (drawable == null) return; @@ -110,4 +132,123 @@ public class RotateImageView extends ImageView { drawable.draw(canvas); canvas.restoreToCount(saveCount); } + + private Bitmap mThumb; + private Drawable[] mThumbs; + private TransitionDrawable mThumbTransition; + + public void setData(Uri uri, Bitmap original) { + // Make sure uri and original are consistently both null or both + // non-null. + if (uri == null || original == null) { + uri = null; + original = null; + } + mUri = uri; + updateThumb(original); + } + + public Uri getUri() { + return mUri; + } + + private static final int BUFSIZE = 4096; + + // Stores the data from the specified file. + // Returns true for success. + public boolean storeData(String filePath) { + if (mUri == null) { + return false; + } + + FileOutputStream f = null; + BufferedOutputStream b = null; + DataOutputStream d = null; + try { + f = new FileOutputStream(filePath); + b = new BufferedOutputStream(f, BUFSIZE); + d = new DataOutputStream(b); + d.writeUTF(mUri.toString()); + mThumb.compress(Bitmap.CompressFormat.PNG, 100, d); + d.close(); + } catch (IOException e) { + return false; + } finally { + MenuHelper.closeSilently(f); + MenuHelper.closeSilently(b); + MenuHelper.closeSilently(d); + } + return true; + } + + // Loads the data from the specified file. + // Returns true for success. + public boolean loadData(String filePath) { + FileInputStream f = null; + BufferedInputStream b = null; + DataInputStream d = null; + try { + f = new FileInputStream(filePath); + b = new BufferedInputStream(f, BUFSIZE); + d = new DataInputStream(b); + Uri uri = Uri.parse(d.readUTF()); + Bitmap thumb = BitmapFactory.decodeStream(d); + setData(uri, thumb); + d.close(); + } catch (IOException e) { + return false; + } finally { + MenuHelper.closeSilently(f); + MenuHelper.closeSilently(b); + MenuHelper.closeSilently(d); + } + return true; + } + + private void updateThumb(Bitmap original) { + if (original == null) { + mThumb = null; + mThumbs = null; + setImageDrawable(null); + return; + } + + LayoutParams param = getLayoutParams(); + final int miniThumbWidth = param.width + - getPaddingLeft() - getPaddingRight(); + final int miniThumbHeight = param.height + - getPaddingTop() - getPaddingBottom(); + mThumb = ThumbnailUtils.extractThumbnail( + original, miniThumbWidth, miniThumbHeight); + Drawable drawable; + if (mThumbs == null || !mEnableAnimation) { + mThumbs = new Drawable[2]; + mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb); + setImageDrawable(mThumbs[1]); + } else { + mThumbs[0] = mThumbs[1]; + mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb); + mThumbTransition = new TransitionDrawable(mThumbs); + setImageDrawable(mThumbTransition); + mThumbTransition.startTransition(500); + } + } + + public boolean isUriValid() { + if (mUri == null) { + return false; + } + try { + ParcelFileDescriptor pfd = + getContext().getContentResolver().openFileDescriptor(mUri, "r"); + if (pfd == null) { + Log.e(TAG, "Fail to open URI. URI=" + mUri); + return false; + } + pfd.close(); + } catch (IOException ex) { + return false; + } + return true; + } } diff --git a/src/com/android/camera/ThumbnailAdapter.java b/src/com/android/camera/ThumbnailAdapter.java new file mode 100644 index 0000000..31d3e02 --- /dev/null +++ b/src/com/android/camera/ThumbnailAdapter.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 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; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.net.Uri; +import android.provider.MediaStore; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ResourceCursorAdapter; +import android.util.Log; + +public class ThumbnailAdapter extends ResourceCursorAdapter { + private final String TAG = "ThumbnailAdapter"; + private int idIndex; + private boolean mIsImage; + public ThumbnailAdapter(Context context, int layout, Cursor c, + boolean isImage) { + super(context, layout, c, false); + mIsImage = isImage; + if (mIsImage) { + idIndex = c.getColumnIndexOrThrow(MediaStore.Images.Thumbnails._ID); + } else { + idIndex = c.getColumnIndexOrThrow(MediaStore.Video.Thumbnails._ID); + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + int id = cursor.getInt(idIndex); + Bitmap b; + Uri uri; + RotateImageView v = (RotateImageView) view; + if (mIsImage) { + uri = Uri.withAppendedPath( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id); + if (!uri.equals(v.getUri())) { + b = MediaStore.Images.Thumbnails.getThumbnail( + context.getContentResolver(), id, + MediaStore.Images.Thumbnails.MINI_KIND, null); + v.setData(uri, b); + } + } else { + uri = Uri.withAppendedPath( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "" + id); + if (!uri.equals(v.getUri())) { + b = MediaStore.Video.Thumbnails.getThumbnail( + context.getContentResolver(), id, + MediaStore.Video.Thumbnails.MINI_KIND, null); + v.setData(uri, b); + } + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = super.newView(context, cursor, parent); + ((RotateImageView)view).enableAnimation(false); + return view; + } +} diff --git a/src/com/android/camera/ThumbnailController.java b/src/com/android/camera/ThumbnailController.java deleted file mode 100644 index 6b67cb0..0000000 --- a/src/com/android/camera/ThumbnailController.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2009 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; - - -import android.content.ContentResolver; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.media.ThumbnailUtils; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; -import android.view.ViewGroup.LayoutParams; -import android.widget.ImageView; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * A controller shows thumbnail picture on a button. The thumbnail picture - * corresponds to a URI of the original picture/video. The thumbnail bitmap - * and the URI can be saved to a file (and later loaded from it). - */ -public class ThumbnailController { - - @SuppressWarnings("unused") - private static final String TAG = "ThumbnailController"; - private final ContentResolver mContentResolver; - private Uri mUri; - private Bitmap mThumb; - private final ImageView mButton; - private Drawable[] mThumbs; - private TransitionDrawable mThumbTransition; - private boolean mShouldAnimateThumb; - private final Resources mResources; - - // The "frame" is a drawable we want to put on top of the thumbnail. - public ThumbnailController(Resources resources, - ImageView button, ContentResolver contentResolver) { - mResources = resources; - mButton = button; - mContentResolver = contentResolver; - } - - public void setData(Uri uri, Bitmap original) { - // Make sure uri and original are consistently both null or both - // non-null. - if (uri == null || original == null) { - uri = null; - original = null; - } - mUri = uri; - updateThumb(original); - } - - public Uri getUri() { - return mUri; - } - - private static final int BUFSIZE = 4096; - - // Stores the data from the specified file. - // Returns true for success. - public boolean storeData(String filePath) { - if (mUri == null) { - return false; - } - - FileOutputStream f = null; - BufferedOutputStream b = null; - DataOutputStream d = null; - try { - f = new FileOutputStream(filePath); - b = new BufferedOutputStream(f, BUFSIZE); - d = new DataOutputStream(b); - d.writeUTF(mUri.toString()); - mThumb.compress(Bitmap.CompressFormat.PNG, 100, d); - d.close(); - } catch (IOException e) { - return false; - } finally { - MenuHelper.closeSilently(f); - MenuHelper.closeSilently(b); - MenuHelper.closeSilently(d); - } - return true; - } - - // Loads the data from the specified file. - // Returns true for success. - public boolean loadData(String filePath) { - FileInputStream f = null; - BufferedInputStream b = null; - DataInputStream d = null; - try { - f = new FileInputStream(filePath); - b = new BufferedInputStream(f, BUFSIZE); - d = new DataInputStream(b); - Uri uri = Uri.parse(d.readUTF()); - Bitmap thumb = BitmapFactory.decodeStream(d); - setData(uri, thumb); - d.close(); - } catch (IOException e) { - return false; - } finally { - MenuHelper.closeSilently(f); - MenuHelper.closeSilently(b); - MenuHelper.closeSilently(d); - } - return true; - } - - public void updateDisplayIfNeeded() { - if (mUri == null) { - mButton.setImageDrawable(null); - return; - } - - if (mShouldAnimateThumb) { - mThumbTransition.startTransition(500); - mShouldAnimateThumb = false; - } - } - - private void updateThumb(Bitmap original) { - if (original == null) { - mThumb = null; - mThumbs = null; - return; - } - - LayoutParams param = mButton.getLayoutParams(); - final int miniThumbWidth = param.width - - mButton.getPaddingLeft() - mButton.getPaddingRight(); - final int miniThumbHeight = param.height - - mButton.getPaddingTop() - mButton.getPaddingBottom(); - mThumb = ThumbnailUtils.extractThumbnail( - original, miniThumbWidth, miniThumbHeight); - Drawable drawable; - if (mThumbs == null) { - mThumbs = new Drawable[2]; - mThumbs[1] = new BitmapDrawable(mResources, mThumb); - drawable = mThumbs[1]; - mShouldAnimateThumb = false; - } else { - mThumbs[0] = mThumbs[1]; - mThumbs[1] = new BitmapDrawable(mResources, mThumb); - mThumbTransition = new TransitionDrawable(mThumbs); - drawable = mThumbTransition; - mShouldAnimateThumb = true; - } - mButton.setImageDrawable(drawable); - } - - public boolean isUriValid() { - if (mUri == null) { - return false; - } - try { - ParcelFileDescriptor pfd = - mContentResolver.openFileDescriptor(mUri, "r"); - if (pfd == null) { - Log.e(TAG, "Fail to open URI."); - return false; - } - pfd.close(); - } catch (IOException ex) { - return false; - } - return true; - } -} diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index 27e3641..91af1e6 100644 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -33,6 +33,7 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.hardware.Camera.Parameters; @@ -50,8 +51,9 @@ import android.os.Message; import android.os.StatFs; import android.os.SystemClock; import android.provider.MediaStore; -import android.provider.Settings; import android.provider.MediaStore.Video; +import android.provider.MediaStore.Video.VideoColumns; +import android.provider.Settings; import android.util.Log; import android.view.Display; import android.view.KeyEvent; @@ -67,8 +69,12 @@ import android.view.WindowManager; import android.view.MenuItem.OnMenuItemClickListener; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.CursorAdapter; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; @@ -135,10 +141,15 @@ public class VideoCamera extends NoSearchActivity private boolean mIsVideoCaptureIntent; private boolean mQuickCapture; - // mLastPictureButton and mThumbController - // are non-null only if mIsVideoCaptureIntent is true. - private ImageView mLastPictureButton; - private ThumbnailController mThumbController; + + // The layout of small devices has a thumbnail button, which shows the last + // captured picture. + private RotateImageView mThumbnailButton; + // The layout of xlarge devices have a list of thumbnails, which show the + // last captured pictures. + private ListView mThumbnailList; + private OnItemClickListener mThumbnailItemClickListener = + new ThumbnailItemClickListener(); private boolean mStartPreviewFail = false; private int mStorageStatus = STORAGE_STATUS_OK; @@ -348,12 +359,7 @@ public class VideoCamera extends NoSearchActivity if (!mIsVideoCaptureIntent) { View controlBar = inflater.inflate( R.layout.camera_control, rootView); - mLastPictureButton = - (ImageView) controlBar.findViewById(R.id.review_thumbnail); - mThumbController = new ThumbnailController( - getResources(), mLastPictureButton, mContentResolver); - mLastPictureButton.setOnClickListener(this); - mThumbController.loadData(ImageManager.getLastVideoThumbPath()); + initThumbnailButton(); mSwitcher = ((Switcher) findViewById(R.id.camera_switch)); mSwitcher.setOnSwitchListener(this); mSwitcher.addTouchView(findViewById(R.id.camera_switch_set)); @@ -460,10 +466,11 @@ public class VideoCamera extends NoSearchActivity doReturnToCaller(true); break; case R.id.btn_cancel: - stopVideoRecordingAndReturn(false); + stopVideoRecording(); + doReturnToCaller(false); break; case R.id.review_thumbnail: - if (!mMediaRecorderRecording) viewLastVideo(); + if (!mMediaRecorderRecording) viewVideo(mThumbnailButton); break; } } @@ -473,14 +480,15 @@ public class VideoCamera extends NoSearchActivity } private void onStopVideoRecording(boolean valid) { + stopVideoRecording(); if (mIsVideoCaptureIntent) { if (mQuickCapture) { - stopVideoRecordingAndReturn(valid); + doReturnToCaller(valid); } else { - stopVideoRecordingAndShowAlert(); + showAlert(); } } else { - stopVideoRecordingAndGetThumbnail(); + getThumbnail(); initializeRecorder(); } } @@ -508,7 +516,8 @@ public class VideoCamera extends NoSearchActivity private void discardCurrentVideoAndInitRecorder() { deleteCurrentVideo(); - hideAlertAndInitializeRecorder(); + hideAlert(); + mHandler.sendEmptyMessage(INIT_RECORDER); } private OnScreenHint mStorageHint; @@ -662,6 +671,13 @@ public class VideoCamera extends NoSearchActivity } changeHeadUpDisplayState(); + // Update the last video thumbnail. + if (!mIsVideoCaptureIntent) { + if (mThumbnailButton != null && !mThumbnailButton.isUriValid()) { + updateThumbnailButton(); + } + updateThumbnailList(); + } } private void setPreviewDisplay(SurfaceHolder holder) { @@ -727,7 +743,8 @@ public class VideoCamera extends NoSearchActivity stopVideoRecording(); showAlert(); } else { - stopVideoRecordingAndGetThumbnail(); + stopVideoRecording(); + getThumbnail(); } } else { stopVideoRecording(); @@ -754,8 +771,8 @@ public class VideoCamera extends NoSearchActivity } resetScreenOn(); - if (!mIsVideoCaptureIntent) { - mThumbController.storeData(ImageManager.getLastVideoThumbPath()); + if (!mIsVideoCaptureIntent && mThumbnailButton != null) { + mThumbnailButton.storeData(ImageManager.getLastVideoThumbPath()); } if (mStorageHint != null) { @@ -854,6 +871,7 @@ public class VideoCamera extends NoSearchActivity setPreviewDisplay(holder); mCameraDevice.unlock(); mHandler.sendEmptyMessage(INIT_RECORDER); + initThumbnailList(); } else { stopVideoRecording(); // If video quality changes, the surface will change. But we need to @@ -1033,14 +1051,6 @@ public class VideoCamera extends NoSearchActivity throw new RuntimeException(e); } mMediaRecorderRecording = false; - - // Update the last video thumbnail. - if (!mIsVideoCaptureIntent) { - if (!mThumbController.isUriValid()) { - updateLastVideo(); - } - mThumbController.updateDisplayIfNeeded(); - } } private void releaseMediaRecorder() { @@ -1309,19 +1319,9 @@ public class VideoCamera extends NoSearchActivity mShutterButton.setImageDrawable(drawable); } - private void stopVideoRecordingAndGetThumbnail() { - stopVideoRecording(); + private void getThumbnail() { acquireVideoThumb(); - } - - private void stopVideoRecordingAndReturn(boolean valid) { - stopVideoRecording(); - doReturnToCaller(valid); - } - - private void stopVideoRecordingAndShowAlert() { - stopVideoRecording(); - showAlert(); + updateThumbnailList(); } private void showAlert() { @@ -1367,22 +1367,21 @@ public class VideoCamera extends NoSearchActivity return this.mVideoFrame.getVisibility() == View.VISIBLE; } - private void viewLastVideo() { - Intent intent = null; - if (mThumbController.isUriValid()) { - intent = new Intent(Util.REVIEW_ACTION, mThumbController.getUri()); + private void viewVideo(RotateImageView view) { + if(view.isUriValid()) { + Intent intent = new Intent(Util.REVIEW_ACTION, view.getUri()); try { startActivity(intent); } catch (ActivityNotFoundException ex) { try { - intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri()); + intent = new Intent(Intent.ACTION_VIEW, view.getUri()); startActivity(intent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "review video fail", e); + Log.e(TAG, "review video fail. uri=" + view.getUri(), e); } } } else { - Log.e(TAG, "Can't view last video."); + Log.e(TAG, "Uri invalid. uri=" + view.getUri()); } } @@ -1435,22 +1434,23 @@ public class VideoCamera extends NoSearchActivity getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - private void hideAlertAndInitializeRecorder() { - hideAlert(); - mHandler.sendEmptyMessage(INIT_RECORDER); - } - private void acquireVideoThumb() { - Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail( - mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); - mThumbController.setData(mCurrentVideoUri, videoFrame); + if (mThumbnailButton != null) { + Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail( + mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); + mThumbnailButton.setData(mCurrentVideoUri, videoFrame); + } } - private static ImageManager.DataLocation dataLocation() { - return ImageManager.DataLocation.EXTERNAL; + private void initThumbnailButton() { + mThumbnailButton = (RotateImageView)findViewById(R.id.review_thumbnail); + if (mThumbnailButton != null) { + mThumbnailButton.setOnClickListener(this); + mThumbnailButton.loadData(ImageManager.getLastVideoThumbPath()); + } } - private void updateLastVideo() { + private void updateThumbnailButton() { IImageList list = ImageManager.makeImageList( mContentResolver, dataLocation(), @@ -1461,13 +1461,59 @@ public class VideoCamera extends NoSearchActivity if (count > 0) { IImage image = list.getImageAt(count - 1); Uri uri = image.fullSizeImageUri(); - mThumbController.setData(uri, image.miniThumbBitmap()); + mThumbnailButton.setData(uri, image.miniThumbBitmap()); } else { - mThumbController.setData(null, null); + mThumbnailButton.setData(null, null); } list.close(); } + private Cursor getThumbnailsCursor(int thumbnailCount) { + Log.v(TAG, "thumbnailCount=" + thumbnailCount); + String[] projections = { MediaStore.Video.Thumbnails._ID }; + Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + .buildUpon() + .appendQueryParameter("limit", String.valueOf(thumbnailCount)) + .build(); + // TODO: managedQuery is deprecated. Use CursorLoader. + return managedQuery(uri, projections, null, null, + VideoColumns._ID + " DESC"); + } + + private void initThumbnailList() { + mThumbnailList = (ListView) findViewById(R.id.image_list); + if (mThumbnailList != null) { + int width = mThumbnailList.getWidth(); + int height = mThumbnailList.getHeight(); + int thumbnailCount = (height + mThumbnailList.getDividerHeight()) + / (width + mThumbnailList.getDividerHeight()); + Cursor cursor = getThumbnailsCursor(thumbnailCount); + ThumbnailAdapter adapter = new ThumbnailAdapter( + getApplicationContext(), R.layout.thumbnail_item, cursor, + false); + mThumbnailList.setAdapter(adapter); + mThumbnailList.setOnItemClickListener(mThumbnailItemClickListener); + } + } + + private void updateThumbnailList() { + if (mThumbnailList == null) return; + CursorAdapter adapter = (CursorAdapter) mThumbnailList.getAdapter(); + Cursor cursor = adapter.getCursor(); + cursor.requery(); + adapter.notifyDataSetChanged(); + } + + private class ThumbnailItemClickListener implements OnItemClickListener { + public void onItemClick(AdapterView<?> p, View v, int pos, long id) { + viewVideo((RotateImageView)v); + } + } + + private static ImageManager.DataLocation dataLocation() { + return ImageManager.DataLocation.EXTERNAL; + } + private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) { long seconds = milliSeconds / 1000; // round down to compute seconds long minutes = seconds / 60; |