summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWu-cheng Li <wuchengli@google.com>2010-09-17 15:48:59 -0700
committerWu-cheng Li <wuchengli@google.com>2010-09-28 15:11:45 -0700
commit9f73bd9a85d295091fae39dc256a122e1843e2e8 (patch)
tree61468fe0a61b24ec6cd3856629b2b87fedaf9efe
parent6bf9e407d0dc0cd7e2f1f4799987523b107e8c96 (diff)
downloadLegacyCamera-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.xml27
-rw-r--r--res/layout/thumbnail_item.xml21
-rw-r--r--src/com/android/camera/Camera.java173
-rw-r--r--src/com/android/camera/RotateImageView.java147
-rw-r--r--src/com/android/camera/ThumbnailAdapter.java77
-rw-r--r--src/com/android/camera/ThumbnailController.java195
-rw-r--r--src/com/android/camera/VideoCamera.java166
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;