summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/Camera.java222
-rw-r--r--src/com/android/camera/CameraSettings.java21
-rw-r--r--src/com/android/camera/IconListPreference.java11
-rw-r--r--src/com/android/camera/MenuHelper.java1
-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/Util.java49
-rw-r--r--src/com/android/camera/VideoCamera.java413
-rw-r--r--src/com/android/camera/ui/BasicIndicator.java50
-rw-r--r--src/com/android/camera/ui/CamcorderHeadUpDisplay.java14
-rw-r--r--src/com/android/camera/ui/GLListView.java27
-rw-r--r--src/com/android/camera/ui/GLRootView.java10
-rw-r--r--src/com/android/camera/ui/GLView.java24
-rw-r--r--src/com/android/camera/ui/GpsIndicator.java2
-rw-r--r--src/com/android/camera/ui/HeadUpDisplay.java17
-rw-r--r--src/com/android/camera/ui/PopupWindow.java5
-rw-r--r--src/com/android/camera/ui/RotatePane.java8
18 files changed, 815 insertions, 478 deletions
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java
index ea68bed..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();
+ }
+ }
+
+ 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);
}
- mThumbController.updateDisplayIfNeeded();
+ }
+
+ 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,
@@ -1686,53 +1739,6 @@ public class Camera extends NoSearchActivity implements View.OnClickListener,
clearFocusState();
}
- private Size getOptimalPreviewSize(List<Size> sizes, double targetRatio) {
- final double ASPECT_TOLERANCE = 0.05;
- if (sizes == null) return null;
-
- Size optimalSize = null;
- double minDiff = Double.MAX_VALUE;
-
- // Because of bugs of overlay and layout, we sometimes will try to
- // layout the viewfinder in the portrait orientation and thus get the
- // wrong size of mSurfaceView. When we change the preview size, the
- // new overlay will be created before the old one closed, which causes
- // an exception. For now, just get the screen size
-
- Display display = getWindowManager().getDefaultDisplay();
- int targetHeight = Math.min(display.getHeight(), display.getWidth());
-
- if (targetHeight <= 0) {
- // We don't know the size of SurefaceView, use screen height
- WindowManager windowManager = (WindowManager)
- getSystemService(Context.WINDOW_SERVICE);
- targetHeight = windowManager.getDefaultDisplay().getHeight();
- }
-
- // Try to find an size match aspect ratio and size
- for (Size size : sizes) {
- double ratio = (double) size.width / size.height;
- if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
- if (Math.abs(size.height - targetHeight) < minDiff) {
- optimalSize = size;
- minDiff = Math.abs(size.height - targetHeight);
- }
- }
-
- // Cannot find the one match the aspect ratio, ignore the requirement
- if (optimalSize == null) {
- Log.v(TAG, "No preview size match the aspect ratio");
- minDiff = Double.MAX_VALUE;
- for (Size size : sizes) {
- if (Math.abs(size.height - targetHeight) < minDiff) {
- optimalSize = size;
- minDiff = Math.abs(size.height - targetHeight);
- }
- }
- }
- return optimalSize;
- }
-
private static boolean isSupported(String value, List<String> supported) {
return supported == null ? false : supported.indexOf(value) >= 0;
}
@@ -1776,7 +1782,7 @@ public class Camera extends NoSearchActivity implements View.OnClickListener,
// Set a preview size that is closest to the viewfinder height and has
// the right aspect ratio.
List<Size> sizes = mParameters.getSupportedPreviewSizes();
- Size optimalSize = getOptimalPreviewSize(
+ Size optimalSize = Util.getOptimalPreviewSize(this,
sizes, (double) size.width / size.height);
if (optimalSize != null) {
Size original = mParameters.getPreviewSize();
@@ -1940,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/CameraSettings.java b/src/com/android/camera/CameraSettings.java
index 2c6bdf1..cde3d9d 100644
--- a/src/com/android/camera/CameraSettings.java
+++ b/src/com/android/camera/CameraSettings.java
@@ -39,6 +39,8 @@ public class CameraSettings {
public static final String KEY_LOCAL_VERSION = "pref_local_version_key";
public static final String KEY_RECORD_LOCATION = RecordLocationPreference.KEY;
public static final String KEY_VIDEO_QUALITY = "pref_video_quality_key";
+ public static final String KEY_VIDEO_TIME_LAPSE_QUALITY = "pref_video_time_lapse_quality_key";
+ public static final String KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL = "pref_video_time_lapse_frame_interval_key";
public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key";
public static final String KEY_JPEG_QUALITY = "pref_camera_jpegquality_key";
public static final String KEY_FOCUS_MODE = "pref_camera_focusmode_key";
@@ -133,6 +135,7 @@ public class CameraSettings {
private void initPreference(PreferenceGroup group) {
ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY);
+ ListPreference videoTimeLapseQuality = group.findPreference(KEY_VIDEO_TIME_LAPSE_QUALITY);
ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);
ListPreference whiteBalance = group.findPreference(KEY_WHITE_BALANCE);
ListPreference colorEffect = group.findPreference(KEY_COLOR_EFFECT);
@@ -163,6 +166,9 @@ public class CameraSettings {
}
// Filter out unsupported settings / options
+ if (videoTimeLapseQuality != null) {
+ filterUnsupportedOptions(group, videoTimeLapseQuality, getSupportedTimeLapseProfiles());
+ }
if (pictureSize != null) {
filterUnsupportedOptions(group, pictureSize, sizeListToStringList(
mParameters.getSupportedPictureSizes()));
@@ -195,6 +201,21 @@ public class CameraSettings {
if (cameraId != null) buildCameraId(group, cameraId);
}
+ private static List<String> getSupportedTimeLapseProfiles() {
+ ArrayList<String> supportedProfiles = new ArrayList<String>();
+ if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_480P)) {
+ supportedProfiles.add(Integer.toString(CamcorderProfile.QUALITY_TIME_LAPSE_480P));
+ }
+ if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_720P)) {
+ supportedProfiles.add(Integer.toString(CamcorderProfile.QUALITY_TIME_LAPSE_720P));
+ }
+ if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_1080P)) {
+ supportedProfiles.add(Integer.toString(CamcorderProfile.QUALITY_TIME_LAPSE_1080P));
+ }
+
+ return supportedProfiles;
+ }
+
private void buildExposureCompensation(
PreferenceGroup group, ListPreference exposure) {
int max = mParameters.getMaxExposureCompensation();
diff --git a/src/com/android/camera/IconListPreference.java b/src/com/android/camera/IconListPreference.java
index fc23f6c..de9cba1 100644
--- a/src/com/android/camera/IconListPreference.java
+++ b/src/com/android/camera/IconListPreference.java
@@ -77,15 +77,18 @@ public class IconListPreference extends ListPreference {
IntArray iconIds = new IntArray();
IntArray largeIconIds = new IntArray();
+ // We allow mIconsIds to be null, but not mLargeIconIds. The reason is that if large icons
+ // are unspecified, the on screen icons will be blank which is a bug.
for (int i = 0, len = entryValues.length; i < len; i++) {
if (supported.indexOf(entryValues[i].toString()) >= 0) {
- iconIds.add(mIconIds[i]);
+ if (mIconIds != null) {
+ iconIds.add(mIconIds[i]);
+ }
largeIconIds.add(mLargeIconIds[i]);
}
}
- int size = iconIds.size();
- mIconIds = iconIds.toArray(new int[size]);
- mLargeIconIds = iconIds.toArray(new int[size]);
+ if (mIconIds != null) mIconIds = iconIds.toArray(new int[iconIds.size()]);
+ mLargeIconIds = largeIconIds.toArray(new int[largeIconIds.size()]);
super.filterUnsupported(supported);
}
}
diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java
index 629cf87..a90726b 100644
--- a/src/com/android/camera/MenuHelper.java
+++ b/src/com/android/camera/MenuHelper.java
@@ -57,6 +57,7 @@ public class MenuHelper {
public static final int POSITION_SWITCH_CAMERA_MODE = 1;
public static final int POSITION_GOTO_GALLERY = 2;
public static final int POSITION_SWITCH_CAMERA_ID = 3;
+ public static final int POSITION_SWITCH_TIME_LAPSE_MODE = 4;
public static final int NO_STORAGE_ERROR = -1;
public static final int CANNOT_STAT_ERROR = -2;
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/Util.java b/src/com/android/camera/Util.java
index adf9152..f7c3316 100644
--- a/src/com/android/camera/Util.java
+++ b/src/com/android/camera/Util.java
@@ -22,7 +22,9 @@ import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
+import android.hardware.Camera.Size;
import android.util.Log;
+import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.animation.Animation;
@@ -31,6 +33,7 @@ import android.view.animation.TranslateAnimation;
import com.android.camera.gallery.IImage;
import com.android.camera.R;
+import java.util.List;
import java.io.Closeable;
/**
@@ -301,4 +304,50 @@ public class Util {
int result = (info.orientation - degrees + 360) % 360;
camera.setDisplayOrientation(result);
}
+
+ public static Size getOptimalPreviewSize(Activity currentActivity,
+ List<Size> sizes, double targetRatio) {
+ final double ASPECT_TOLERANCE = 0.05;
+ if (sizes == null) return null;
+
+ Size optimalSize = null;
+ double minDiff = Double.MAX_VALUE;
+
+ // Because of bugs of overlay and layout, we sometimes will try to
+ // layout the viewfinder in the portrait orientation and thus get the
+ // wrong size of mSurfaceView. When we change the preview size, the
+ // new overlay will be created before the old one closed, which causes
+ // an exception. For now, just get the screen size
+
+ Display display = currentActivity.getWindowManager().getDefaultDisplay();
+ int targetHeight = Math.min(display.getHeight(), display.getWidth());
+
+ if (targetHeight <= 0) {
+ // We don't know the size of SurfaceView, use screen height
+ targetHeight = display.getHeight();
+ }
+
+ // Try to find an size match aspect ratio and size
+ for (Size size : sizes) {
+ double ratio = (double) size.width / size.height;
+ if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
+ if (Math.abs(size.height - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.height - targetHeight);
+ }
+ }
+
+ // Cannot find the one match the aspect ratio, ignore the requirement
+ if (optimalSize == null) {
+ Log.v(TAG, "No preview size match the aspect ratio");
+ minDiff = Double.MAX_VALUE;
+ for (Size size : sizes) {
+ if (Math.abs(size.height - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.height - targetHeight);
+ }
+ }
+ }
+ return optimalSize;
+ }
}
diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java
index a92e33c..9ee58bb 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,9 +51,11 @@ 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;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -66,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;
@@ -130,13 +137,19 @@ public class VideoCamera extends NoSearchActivity
private ImageView mVideoFrame;
private GLRootView mGLRootView;
private CamcorderHeadUpDisplay mHeadUpDisplay;
+ private MenuItem mSwitchTimeLapseMenuItem;
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;
@@ -160,6 +173,13 @@ public class VideoCamera extends NoSearchActivity
// The video duration limit. 0 menas no limit.
private int mMaxVideoDurationInMs;
+ // Time Lapse parameters.
+ private boolean mCaptureTimeLapse = false;
+ private int mTimeBetweenTimeLapseFrameCaptureMs = 2000;
+
+ private int mDesiredPreviewWidth;
+ private int mDesiredPreviewHeight;
+
boolean mPausing = false;
boolean mPreviewing = false; // True if preview is started.
@@ -339,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));
@@ -402,12 +417,11 @@ public class VideoCamera extends NoSearchActivity
CameraSettings settings = new CameraSettings(this, mParameters,
CameraHolder.instance().getCameraInfo());
- PreferenceGroup group =
- settings.getPreferenceGroup(R.xml.video_preferences);
+ PreferenceGroup group = settings.getPreferenceGroup(R.xml.video_preferences);
if (mIsVideoCaptureIntent) {
group = filterPreferenceScreenByIntent(group);
}
- mHeadUpDisplay.initialize(this, group);
+ mHeadUpDisplay.initialize(this, group, mCaptureTimeLapse);
}
private void attachHeadUpDisplay() {
@@ -452,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;
}
}
@@ -465,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();
}
}
@@ -500,7 +516,8 @@ public class VideoCamera extends NoSearchActivity
private void discardCurrentVideoAndInitRecorder() {
deleteCurrentVideo();
- hideAlertAndInitializeRecorder();
+ hideAlert();
+ mHandler.sendEmptyMessage(INIT_RECORDER);
}
private OnScreenHint mStorageHint;
@@ -548,7 +565,39 @@ public class VideoCamera extends NoSearchActivity
: STORAGE_STATUS_OK;
}
+ private void readTimeLapseVideoPreferences() {
+ // Read CamcorderProfile quality.
+ String qualityStr = mPreferences.getString(
+ CameraSettings.KEY_VIDEO_TIME_LAPSE_QUALITY,
+ getString(R.string.pref_video_time_lapse_quality_default));
+ mProfile = CamcorderProfile.get(Integer.parseInt(qualityStr));
+
+ // Read interval between frame capture.
+ String frameIntervalStr = mPreferences.getString(
+ CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+ getString(R.string.pref_video_time_lapse_frame_interval_default));
+ mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
+
+ mMaxVideoDurationInMs = 0; // No limit
+
+ // Time lapse mode can capture video (using the still camera) at resolutions
+ // higher than the supported preview sizes. In that case
+ // mProfile.{videoFrameWidth,videoFrameHeight} will correspond to an unsupported
+ // preview size. So choose preview size optimally from the supported preview
+ // sizes.
+ List<Size> sizes = mParameters.getSupportedPreviewSizes();
+ Size optimalSize = Util.getOptimalPreviewSize(this,
+ sizes, (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+ mDesiredPreviewWidth = optimalSize.width;
+ mDesiredPreviewHeight = optimalSize.height;
+ }
+
private void readVideoPreferences() {
+ if (mCaptureTimeLapse) {
+ readTimeLapseVideoPreferences();
+ return;
+ }
+
String quality = mPreferences.getString(
CameraSettings.KEY_VIDEO_QUALITY,
CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
@@ -577,6 +626,9 @@ public class VideoCamera extends NoSearchActivity
videoQualityHigh
? CamcorderProfile.QUALITY_HIGH
: CamcorderProfile.QUALITY_LOW);
+
+ mDesiredPreviewWidth = mProfile.videoFrameWidth;
+ mDesiredPreviewHeight = mProfile.videoFrameHeight;
}
private void resizeForPreviewAspectRatio() {
@@ -620,6 +672,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) {
@@ -677,17 +736,7 @@ public class VideoCamera extends NoSearchActivity
mPreviewing = false;
}
- @Override
- protected void onPause() {
- super.onPause();
- mPausing = true;
-
- changeHeadUpDisplayState();
-
- // Hide the preview now. Otherwise, the preview may be rotated during
- // onPause and it is annoying to users.
- mVideoPreview.setVisibility(View.INVISIBLE);
-
+ private void finishRecorderAndCloseCamera() {
// This is similar to what mShutterButton.performClick() does,
// but not quite the same.
if (mMediaRecorderRecording) {
@@ -695,12 +744,27 @@ public class VideoCamera extends NoSearchActivity
stopVideoRecording();
showAlert();
} else {
- stopVideoRecordingAndGetThumbnail();
+ stopVideoRecording();
+ getThumbnail();
}
} else {
stopVideoRecording();
}
closeCamera();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mPausing = true;
+
+ changeHeadUpDisplayState();
+
+ // Hide the preview now. Otherwise, the preview may be rotated during
+ // onPause and it is annoying to users.
+ mVideoPreview.setVisibility(View.INVISIBLE);
+
+ finishRecorderAndCloseCamera();
if (mReceiver != null) {
unregisterReceiver(mReceiver);
@@ -708,8 +772,8 @@ public class VideoCamera extends NoSearchActivity
}
resetScreenOn();
- if (!mIsVideoCaptureIntent) {
- mThumbController.storeData(ImageManager.getLastVideoThumbPath());
+ if (!mIsVideoCaptureIntent && mThumbnailButton != null) {
+ mThumbnailButton.storeData(ImageManager.getLastVideoThumbPath());
}
if (mStorageHint != null) {
@@ -808,6 +872,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
@@ -928,10 +993,17 @@ public class VideoCamera extends NoSearchActivity
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setCamera(mCameraDevice);
- mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ if (!mCaptureTimeLapse) {
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ }
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(mProfile);
- mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
+ if (mMaxVideoDurationInMs != 0) {
+ mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
+ }
+ if (mCaptureTimeLapse) {
+ mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
+ }
// Set output file.
if (mStorageStatus != STORAGE_STATUS_OK) {
@@ -980,14 +1052,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() {
@@ -1059,6 +1123,14 @@ public class VideoCamera extends NoSearchActivity
}
}
+ private void setTimeLapseSwitchTitle(boolean enableTimeLapse) {
+ int labelId = enableTimeLapse
+ ? R.string.enable_time_lapse_mode
+ : R.string.disable_time_lapse_mode;
+
+ mSwitchTimeLapseMenuItem.setTitle(labelId);
+ }
+
private void addBaseMenuItems(Menu menu) {
MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() {
public void run() {
@@ -1089,6 +1161,45 @@ public class VideoCamera extends NoSearchActivity
}
}).setIcon(android.R.drawable.ic_menu_camera);
}
+
+ mSwitchTimeLapseMenuItem = menu.add(Menu.NONE, Menu.NONE,
+ MenuHelper.POSITION_SWITCH_TIME_LAPSE_MODE,
+ R.string.enable_time_lapse_mode)
+ .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ switchTimeLapseMode();
+ return true;
+ }
+ }).setIcon(android.R.drawable.ic_menu_camera);
+ }
+
+ private void switchTimeLapseMode() {
+ mCaptureTimeLapse = !mCaptureTimeLapse;
+
+ finishRecorderAndCloseCamera();
+ mHandler.removeMessages(INIT_RECORDER);
+
+ // Read the video preferences
+ readVideoPreferences();
+ resetCameraParameters();
+
+ // Restart preview
+ try {
+ startPreview();
+ } catch (CameraHardwareException e) {
+ showCameraErrorAndFinish();
+ return;
+ }
+
+ // Reload the UI.
+ initializeHeadUpDisplay();
+
+ if (mSurfaceHolder != null) {
+ mHandler.sendEmptyMessage(INIT_RECORDER);
+ }
+
+ // Change menu
+ setTimeLapseSwitchTitle(!mCaptureTimeLapse);
}
private void switchCameraId(int cameraId) {
@@ -1096,19 +1207,7 @@ public class VideoCamera extends NoSearchActivity
mCameraId = cameraId;
CameraSettings.writePreferredCameraId(mPreferences, cameraId);
- // This is similar to what mShutterButton.performClick() does,
- // but not quite the same.
- if (mMediaRecorderRecording) {
- if (mIsVideoCaptureIntent) {
- stopVideoRecording();
- showAlert();
- } else {
- stopVideoRecordingAndGetThumbnail();
- }
- } else {
- stopVideoRecording();
- }
- closeCamera();
+ finishRecorderAndCloseCamera();
mHandler.removeMessages(INIT_RECORDER);
// Reload the preferences.
@@ -1221,19 +1320,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() {
@@ -1279,22 +1368,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());
}
}
@@ -1347,22 +1435,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(),
@@ -1373,13 +1462,115 @@ 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;
+ long hours = minutes / 60;
+ long remainderMinutes = minutes - (hours * 60);
+ long remainderSeconds = seconds - (minutes * 60);
+
+ StringBuilder timeStringBuilder = new StringBuilder();
+
+ // Hours
+ if (hours > 0) {
+ if (hours < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(hours);
+
+ timeStringBuilder.append(':');
+ }
+
+ // Minutes
+ if (remainderMinutes < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(remainderMinutes);
+ timeStringBuilder.append(':');
+
+ // Seconds
+ if (remainderSeconds < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(remainderSeconds);
+
+ // Centi seconds
+ if (displayCentiSeconds) {
+ timeStringBuilder.append('.');
+ long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
+ if (remainderCentiSeconds < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(remainderCentiSeconds);
+ }
+
+ return timeStringBuilder.toString();
+ }
+
+ // Calculates the time lapse video length till now and returns it in
+ // the format hh:mm:ss.dd, where dd are the centi seconds.
+ private String getTimeLapseVideoLengthString(long deltaMs) {
+ // For better approximation calculate fractional number of frames captured.
+ // This will update the video time at a higher resolution.
+ double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
+ long videoTimeMs =
+ (long) (numberOfFrames / (double) mProfile.videoFrameRate * 1000);
+ return millisecondToTimeString(videoTimeMs, true);
+ }
+
private void updateRecordingTime() {
if (!mMediaRecorderRecording) {
return;
@@ -1392,36 +1583,19 @@ public class VideoCamera extends NoSearchActivity
boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
&& delta >= mMaxVideoDurationInMs - 60000);
- long next_update_delay = 1000 - (delta % 1000);
- long seconds;
+ long deltaAdjusted = delta;
if (countdownRemainingTime) {
- delta = Math.max(0, mMaxVideoDurationInMs - delta);
- seconds = (delta + 999) / 1000;
- } else {
- seconds = delta / 1000; // round to nearest
+ deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
}
+ String text = millisecondToTimeString(deltaAdjusted, false);
- long minutes = seconds / 60;
- long hours = minutes / 60;
- long remainderMinutes = minutes - (hours * 60);
- long remainderSeconds = seconds - (minutes * 60);
-
- String secondsString = Long.toString(remainderSeconds);
- if (secondsString.length() < 2) {
- secondsString = "0" + secondsString;
- }
- String minutesString = Long.toString(remainderMinutes);
- if (minutesString.length() < 2) {
- minutesString = "0" + minutesString;
- }
- String text = minutesString + ":" + secondsString;
- if (hours > 0) {
- String hoursString = Long.toString(hours);
- if (hoursString.length() < 2) {
- hoursString = "0" + hoursString;
- }
- text = hoursString + ":" + text;
+ if (mCaptureTimeLapse) {
+ // Since the length of time lapse video is different from the length
+ // of the actual wall clock time elapsed, we display the video length
+ // alongside the wall clock time.
+ text = text + " (" + getTimeLapseVideoLengthString(delta) + ")";
}
+
mRecordingTimeView.setText(text);
if (mRecordingTimeCountsDown != countdownRemainingTime) {
@@ -1436,8 +1610,9 @@ public class VideoCamera extends NoSearchActivity
mRecordingTimeView.setTextColor(color);
}
+ long nextUpdateDelay = 1000 - (delta % 1000);
mHandler.sendEmptyMessageDelayed(
- UPDATE_RECORD_TIME, next_update_delay);
+ UPDATE_RECORD_TIME, nextUpdateDelay);
}
private static boolean isSupported(String value, List<String> supported) {
@@ -1447,7 +1622,7 @@ public class VideoCamera extends NoSearchActivity
private void setCameraParameters() {
mParameters = mCameraDevice.getParameters();
- mParameters.setPreviewSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
+ mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
// Set flash mode.
@@ -1520,8 +1695,8 @@ public class VideoCamera extends NoSearchActivity
private void resetCameraParameters() {
// We need to restart the preview if preview size is changed.
Size size = mParameters.getPreviewSize();
- if (size.width != mProfile.videoFrameWidth
- || size.height != mProfile.videoFrameHeight) {
+ if (size.width != mDesiredPreviewWidth
+ || size.height != mDesiredPreviewHeight) {
// It is assumed media recorder is released before
// onSharedPreferenceChanged, so we can close the camera here.
closeCamera();
diff --git a/src/com/android/camera/ui/BasicIndicator.java b/src/com/android/camera/ui/BasicIndicator.java
index 7630a8e..9f076b8 100644
--- a/src/com/android/camera/ui/BasicIndicator.java
+++ b/src/com/android/camera/ui/BasicIndicator.java
@@ -19,31 +19,47 @@ package com.android.camera.ui;
import android.content.Context;
import com.android.camera.IconListPreference;
+import com.android.camera.ListPreference;
import com.android.camera.R;
import com.android.camera.Util;
import com.android.camera.ui.GLListView.OnItemSelectedListener;
class BasicIndicator extends AbstractIndicator {
+ private static final float FONT_SIZE = 18;
+ private static final int FONT_COLOR = 0xA8FFFFFF;
private static final int COLOR_OPTION_ITEM_HIGHLIGHT = 0xFF181818;
private final ResourceTexture mIcon[];
- private final IconListPreference mPreference;
+ private final ListPreference mPreference;
protected int mIndex;
private GLListView mPopupContent;
private PreferenceAdapter mModel;
private String mOverride;
+ private int mTitleIndex;
+ private StringTexture mTitle;
+ private final float mFontSize;
+ private boolean mIsIconListMode;
- public BasicIndicator(Context context, IconListPreference preference) {
+ public BasicIndicator(Context context, ListPreference preference) {
super(context);
mPreference = preference;
- mIcon = new ResourceTexture[preference.getLargeIconIds().length];
mIndex = preference.findIndexOfValue(preference.getValue());
+ if (preference instanceof IconListPreference) {
+ mIsIconListMode = true;
+ mIcon = new ResourceTexture[((IconListPreference) preference).getLargeIconIds().length];
+ mFontSize = 0;
+ } else {
+ mIsIconListMode = false;
+ mIcon = null;
+ mFontSize = GLRootView.dpToPixel(context, FONT_SIZE);
+ mTitleIndex = -1;
+ }
}
// Set the override and/or reload the value from preferences.
private void updateContent(String override, boolean reloadValue) {
if (!reloadValue && Util.equals(mOverride, override)) return;
- IconListPreference pref = mPreference;
+ ListPreference pref = mPreference;
mOverride = override;
int index = pref.findIndexOfValue(
override == null ? pref.getValue() : override);
@@ -55,7 +71,7 @@ class BasicIndicator extends AbstractIndicator {
@Override
public void overrideSettings(String key, String settings) {
- IconListPreference pref = mPreference;
+ ListPreference pref = mPreference;
if (!pref.getKey().equals(key)) return;
updateContent(settings, false);
}
@@ -104,13 +120,23 @@ class BasicIndicator extends AbstractIndicator {
}
@Override
- protected ResourceTexture getIcon() {
- int index = mIndex;
- if (mIcon[index] == null) {
- Context context = getGLRootView().getContext();
- mIcon[index] = new ResourceTexture(
- context, mPreference.getLargeIconIds()[index]);
+ protected BitmapTexture getIcon() {
+ if (mIsIconListMode) {
+ int index = mIndex;
+ if (mIcon[index] == null) {
+ Context context = getGLRootView().getContext();
+ mIcon[index] = new ResourceTexture(
+ context, ((IconListPreference) mPreference).getLargeIconIds()[index]);
+ }
+ return mIcon[index];
+ } else {
+ if (mTitleIndex != mIndex) {
+ mTitleIndex = mIndex;
+ if (mTitle != null) mTitle.deleteFromGL();
+ String value = mPreference.getEntry();
+ mTitle = StringTexture.newInstance(value, mFontSize, FONT_COLOR);
+ }
+ return mTitle;
}
- return mIcon[index];
}
}
diff --git a/src/com/android/camera/ui/CamcorderHeadUpDisplay.java b/src/com/android/camera/ui/CamcorderHeadUpDisplay.java
index 8e98100..c69b92b 100644
--- a/src/com/android/camera/ui/CamcorderHeadUpDisplay.java
+++ b/src/com/android/camera/ui/CamcorderHeadUpDisplay.java
@@ -26,12 +26,19 @@ public class CamcorderHeadUpDisplay extends HeadUpDisplay {
private static final String TAG = "CamcorderHeadUpDisplay";
+ private boolean mCaptureTimeLapse;
private OtherSettingsIndicator mOtherSettings;
public CamcorderHeadUpDisplay(Context context) {
super(context);
}
+ public void initialize(Context context, PreferenceGroup group,
+ boolean captureTimeLapse) {
+ mCaptureTimeLapse = captureTimeLapse;
+ super.initialize(context, group);
+ }
+
@Override
protected void initializeIndicatorBar(
Context context, PreferenceGroup group) {
@@ -57,7 +64,12 @@ public class CamcorderHeadUpDisplay extends HeadUpDisplay {
addIndicator(context, group, CameraSettings.KEY_WHITE_BALANCE);
addIndicator(context, group, CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE);
- addIndicator(context, group, CameraSettings.KEY_VIDEO_QUALITY);
+ if (mCaptureTimeLapse) {
+ addIndicator(context, group, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
+ addIndicator(context, group, CameraSettings.KEY_VIDEO_TIME_LAPSE_QUALITY);
+ } else {
+ addIndicator(context, group, CameraSettings.KEY_VIDEO_QUALITY);
+ }
addIndicator(context, group, CameraSettings.KEY_CAMERA_ID);
}
}
diff --git a/src/com/android/camera/ui/GLListView.java b/src/com/android/camera/ui/GLListView.java
index c1bc11a..ed22ea3 100644
--- a/src/com/android/camera/ui/GLListView.java
+++ b/src/com/android/camera/ui/GLListView.java
@@ -17,10 +17,12 @@
package com.android.camera.ui;
import static android.view.View.MeasureSpec.makeMeasureSpec;
+import com.android.camera.Util;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -30,15 +32,13 @@ import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.Scroller;
-import com.android.camera.Util;
-
import javax.microedition.khronos.opengles.GL11;
class GLListView extends GLView {
@SuppressWarnings("unused")
private static final String TAG = "GLListView";
private static final int INDEX_NONE = -1;
- private static final int SCROLL_BAR_TIMEOUT = 2500;
+ private static final int SCROLL_BAR_TIMEOUT = 1000;
private static final int HIDE_SCROLL_BAR = 1;
@@ -77,7 +77,7 @@ class GLListView extends GLView {
public GLListView(Context context) {
mScroller = new Scroller(context);
- mHandler = new Handler() {
+ mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
GLRootView root = getGLRootView();
@@ -102,13 +102,25 @@ class GLListView extends GLView {
context, new MyGestureListener(), mHandler);
}
+ private final Runnable mHideScrollBar = new Runnable() {
+ public void run() {
+ setScrollBarVisible(false);
+ }
+ };
+
@Override
protected void onVisibilityChanged(int visibility) {
super.onVisibilityChanged(visibility);
- if (visibility == GLView.VISIBLE && mScrollHeight > getHeight()) {
+ if (mScrollHeight > getHeight()) updateScrollBar(visibility);
+ }
+
+ private void updateScrollBar(int visibility) {
+ if (isVisible()) {
setScrollBarVisible(true);
mHandler.sendEmptyMessageDelayed(
HIDE_SCROLL_BAR, SCROLL_BAR_TIMEOUT);
+ } else {
+ mHandler.removeMessages(HIDE_SCROLL_BAR);
}
}
@@ -294,7 +306,10 @@ class GLListView extends GLView {
mIsPressed = true;
mHandler.removeMessages(HIDE_SCROLL_BAR);
setScrollBarVisible(mScrollHeight > getHeight());
-
+ if (!mScroller.isFinished()) {
+ mScroller.forceFinished(true);
+ break;
+ }
// fallthrough: we need to highlight the item which is pressed
case MotionEvent.ACTION_MOVE:
if (!mScrollable) {
diff --git a/src/com/android/camera/ui/GLRootView.java b/src/com/android/camera/ui/GLRootView.java
index e7ac5e3..bdb0a8e 100644
--- a/src/com/android/camera/ui/GLRootView.java
+++ b/src/com/android/camera/ui/GLRootView.java
@@ -30,6 +30,7 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
@@ -37,7 +38,6 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Stack;
-
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
@@ -728,4 +728,12 @@ public class GLRootView extends GLSurfaceView
texture.setTextureSize(newWidth, newHeight);
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int v) {
+ super.onVisibilityChanged(changedView, v);
+ if (mContentView != null) {
+ mContentView.onVisibilityChanged(
+ v == View.VISIBLE ? GLView.VISIBLE : GLView.INVISIBLE);
+ }
+ }
}
diff --git a/src/com/android/camera/ui/GLView.java b/src/com/android/camera/ui/GLView.java
index 184016e..811527a 100644
--- a/src/com/android/camera/ui/GLView.java
+++ b/src/com/android/camera/ui/GLView.java
@@ -20,11 +20,11 @@ import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.MotionEvent;
+import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import java.util.ArrayList;
-
import javax.microedition.khronos.opengles.GL11;
public class GLView {
@@ -81,8 +81,22 @@ public class GLView {
} else {
mViewFlags |= FLAG_INVISIBLE;
}
- onVisibilityChanged(visibility);
invalidate();
+
+ // Trigger the onVisibilityChanged() if it is visible on the screen.
+ if (isVisible()) onVisibilityChanged(visibility);
+ }
+
+ public boolean isVisible() {
+ if (mRootView == null || mRootView.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ GLView parent = mParent;
+ while (parent != null) {
+ if (parent.getVisibility() == GLView.INVISIBLE) return false;
+ parent = parent.mParent;
+ }
+ return true;
}
public int getVisibility() {
@@ -141,6 +155,7 @@ public class GLView {
}
mComponents.add(component);
component.onAddToParent(this);
+ if (isVisible()) component.onVisibilityChanged(VISIBLE);
}
public boolean removeComponent(GLView component) {
@@ -362,10 +377,7 @@ public class GLView {
protected void onVisibilityChanged(int visibility) {
for (int i = 0, n = getComponentCount(); i < n; ++i) {
- GLView child = getComponent(i);
- if (child.getVisibility() == GLView.VISIBLE) {
- child.onVisibilityChanged(visibility);
- }
+ getComponent(i).onVisibilityChanged(visibility);
}
}
diff --git a/src/com/android/camera/ui/GpsIndicator.java b/src/com/android/camera/ui/GpsIndicator.java
index 78a80b8..c1cb2ef 100644
--- a/src/com/android/camera/ui/GpsIndicator.java
+++ b/src/com/android/camera/ui/GpsIndicator.java
@@ -33,7 +33,7 @@ class GpsIndicator extends BasicIndicator {
}
@Override
- protected ResourceTexture getIcon() {
+ protected BitmapTexture getIcon() {
if (mIndex == GPS_ON_INDEX && !mHasSignal) {
if (mNoSignalIcon == null) {
Context context = getGLRootView().getContext();
diff --git a/src/com/android/camera/ui/HeadUpDisplay.java b/src/com/android/camera/ui/HeadUpDisplay.java
index 6a7c604..8751d09 100644
--- a/src/com/android/camera/ui/HeadUpDisplay.java
+++ b/src/com/android/camera/ui/HeadUpDisplay.java
@@ -17,8 +17,11 @@
package com.android.camera.ui;
import static com.android.camera.ui.GLRootView.dpToPixel;
-
-import java.util.ArrayList;
+import com.android.camera.CameraSettings;
+import com.android.camera.ComboPreferences;
+import com.android.camera.ListPreference;
+import com.android.camera.PreferenceGroup;
+import com.android.camera.R;
import android.content.Context;
import android.content.SharedPreferences;
@@ -34,12 +37,7 @@ import android.view.View.MeasureSpec;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import com.android.camera.CameraSettings;
-import com.android.camera.ComboPreferences;
-import com.android.camera.IconListPreference;
-import com.android.camera.ListPreference;
-import com.android.camera.PreferenceGroup;
-import com.android.camera.R;
+import java.util.ArrayList;
// This is the UI for the on-screen settings. Since the rendering is run in the
// GL thread. If any values will be changed in the main thread, it needs to
@@ -329,8 +327,7 @@ public class HeadUpDisplay extends GLView {
protected BasicIndicator addIndicator(
Context context, PreferenceGroup group, String key) {
- IconListPreference iconPref =
- (IconListPreference) group.findPreference(key);
+ ListPreference iconPref = group.findPreference(key);
if (iconPref == null) return null;
BasicIndicator indicator = new BasicIndicator(context, iconPref);
mIndicatorBar.addComponent(indicator);
diff --git a/src/com/android/camera/ui/PopupWindow.java b/src/com/android/camera/ui/PopupWindow.java
index c2bf533..cc76f46 100644
--- a/src/com/android/camera/ui/PopupWindow.java
+++ b/src/com/android/camera/ui/PopupWindow.java
@@ -186,11 +186,6 @@ class PopupWindow extends GLView {
mRotatePane.setContent(content);
}
- @Override
- public void clearComponents() {
- throw new UnsupportedOperationException();
- }
-
public void popup() {
setVisibility(GLView.VISIBLE);
diff --git a/src/com/android/camera/ui/RotatePane.java b/src/com/android/camera/ui/RotatePane.java
index 9f9effa..45f6a0d 100644
--- a/src/com/android/camera/ui/RotatePane.java
+++ b/src/com/android/camera/ui/RotatePane.java
@@ -117,8 +117,7 @@ class RotatePane extends GLView {
public void setContent(GLView view) {
if (mChild == view) return;
-
- if (mChild != null) super.clearComponents();
+ if (mChild != null) super.removeComponent(mChild);
mChild = view;
if (view != null) super.addComponent(view);
requestLayout();
@@ -128,9 +127,4 @@ class RotatePane extends GLView {
public void addComponent(GLView view) {
throw new UnsupportedOperationException("use setContent(GLView)");
}
-
- @Override
- public void clearComponents() {
- throw new UnsupportedOperationException("use setContent(null)");
- }
}