diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-01-15 16:12:13 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-01-15 16:12:13 -0800 |
commit | 9c061072c8f4ec16acf25e0af7ca3d8317d1026f (patch) | |
tree | ed8b90b59a2688e918680d1fe530647a2c1e75b4 /src | |
parent | b97ccf3f20bee44daf70f10966809e39e30ab4f7 (diff) | |
download | LegacyCamera-9c061072c8f4ec16acf25e0af7ca3d8317d1026f.zip LegacyCamera-9c061072c8f4ec16acf25e0af7ca3d8317d1026f.tar.gz LegacyCamera-9c061072c8f4ec16acf25e0af7ca3d8317d1026f.tar.bz2 |
auto import from //branches/cupcake/...@126645
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/camera/Camera.java | 91 | ||||
-rw-r--r-- | src/com/android/camera/CropImage.java | 161 | ||||
-rw-r--r-- | src/com/android/camera/GalleryPicker.java | 25 | ||||
-rw-r--r-- | src/com/android/camera/MenuHelper.java | 71 | ||||
-rw-r--r-- | src/com/android/camera/MovieView.java | 97 | ||||
-rw-r--r-- | src/com/android/camera/VideoCamera.java | 136 |
6 files changed, 356 insertions, 225 deletions
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index 993e15c..ed2f7a4 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -94,9 +94,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol private static final int SCREEN_DELAY = 2 * 60 * 1000; private static final int FOCUS_BEEP_VOLUME = 100; - private static final int NO_STORAGE_ERROR = -1; - private static final int CANNOT_STAT_ERROR = -2; - public static final int MENU_SWITCH_TO_VIDEO = 0; public static final int MENU_SWITCH_TO_CAMERA = 1; public static final int MENU_FLASH_SETTING = 2; @@ -742,6 +739,8 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mLastPictureButton.setOnClickListener(this); mLastPictureButton.setVisibility(View.INVISIBLE); + findViewById(R.id.mode_indicator).setOnClickListener(this); + try { mClickSound = new MediaPlayer(); AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.camera_click); @@ -814,27 +813,17 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol public void onClick(View v) { switch (v.getId()) { - - case R.id.last_picture_button: { - viewLastImage(); - break; - } + case R.id.mode_indicator: + doSnap(true); + break; + case R.id.last_picture_button: + viewLastImage(); + break; } } private void showStorageToast() { - String noStorageText = null; - int remaining = calculatePicturesRemaining(); - - if (remaining == NO_STORAGE_ERROR) { - noStorageText = getString(R.string.no_storage); - } else if (remaining < 1) { - noStorageText = getString(R.string.not_enough_space); - } - - if (noStorageText != null) { - Toast.makeText(this, noStorageText, 5000).show(); - } + MenuHelper.showStorageToast(this); } @Override @@ -1007,27 +996,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol case KeyEvent.KEYCODE_CAMERA: case KeyEvent.KEYCODE_DPAD_CENTER: if (event.getRepeatCount() == 0) { - // The camera operates in focus-priority mode, meaning that we take a picture - // when focusing completes, and only if it completes successfully. If the user - // has half-pressed the shutter and already locked focus, we can take the photo - // right away, otherwise we need to start AF. - if (mIsFocused || !mPreviewing) { - // doesn't get set until the idler runs - if (mCaptureObject != null) { - mCaptureObject.onSnap(); - } - clearFocus(); - updateFocusIndicator(); - } else { - // Half pressing the shutter (i.e. the focus button event) will already have - // requested AF for us, so just request capture on focus here. If AF has - // already failed, we don't want to trigger it again. - mCaptureOnFocus = true; - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && !mIsFocusButtonPressed) { - // But we do need to start AF for DPAD_CENTER - autoFocus(); - } - } + doSnap(keyCode == KeyEvent.KEYCODE_DPAD_CENTER); } return true; } @@ -1046,6 +1015,30 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol return super.onKeyUp(keyCode, event); } + private void doSnap(boolean needAutofocus) { + // The camera operates in focus-priority mode, meaning that we take a picture + // when focusing completes, and only if it completes successfully. If the user + // has half-pressed the shutter and already locked focus, we can take the photo + // right away, otherwise we need to start AF. + if (mIsFocused || !mPreviewing) { + // doesn't get set until the idler runs + if (mCaptureObject != null) { + mCaptureObject.onSnap(); + } + clearFocus(); + updateFocusIndicator(); + } else { + // Half pressing the shutter (i.e. the focus button event) will already have + // requested AF for us, so just request capture on focus here. If AF has + // already failed, we don't want to trigger it again. + mCaptureOnFocus = true; + if (needAutofocus && !mIsFocusButtonPressed) { + // But we do need to start AF for DPAD_CENTER + autoFocus(); + } + } + } + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // if we're creating the surface, start the preview as well. boolean preview = holder.isCreating(); @@ -1571,21 +1564,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol }; private int calculatePicturesRemaining() { - try { - if (!ImageManager.instance().hasStorage()) { - mPicturesRemaining = NO_STORAGE_ERROR; - } else { - String storageDirectory = Environment.getExternalStorageDirectory().toString(); - StatFs stat = new StatFs(storageDirectory); - float remaining = ((float)stat.getAvailableBlocks() * (float)stat.getBlockSize()) / 400000F; - mPicturesRemaining = (int)remaining; - } - } catch (Exception ex) { - // if we can't stat the filesystem then we don't know how many - // pictures are remaining. it might be zero but just leave it - // blank since we really don't know. - mPicturesRemaining = CANNOT_STAT_ERROR; - } + mPicturesRemaining = MenuHelper.calculatePicturesRemaining(); return mPicturesRemaining; } diff --git a/src/com/android/camera/CropImage.java b/src/com/android/camera/CropImage.java index a7f6404..489c644 100644 --- a/src/com/android/camera/CropImage.java +++ b/src/com/android/camera/CropImage.java @@ -87,7 +87,7 @@ public class CropImage extends Activity { HighlightView mMotionHighlightView = null; float mLastX, mLastY; int mMotionEdge; - + public CropImageView(Context context) { super(context); } @@ -96,7 +96,7 @@ public class CropImage extends Activity { protected boolean doesScrolling() { return false; } - + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -110,11 +110,11 @@ public class CropImage extends Activity { } } } - + public CropImageView(Context context, AttributeSet attrs) { super(context, attrs); } - + protected void zoomTo(float scale, float centerX, float centerY) { super.zoomTo(scale, centerX, centerY); for (HighlightView hv : mHighlightViews) { @@ -130,7 +130,7 @@ public class CropImage extends Activity { hv.invalidate(); } } - + protected void zoomOut() { super.zoomOut(); for (HighlightView hv : mHighlightViews) { @@ -139,12 +139,12 @@ public class CropImage extends Activity { } } - + @Override protected boolean usePerfectFitBitmap() { return false; } - + @Override protected void postTranslate(float deltaX, float deltaY) { super.postTranslate(deltaX, deltaY); @@ -161,7 +161,7 @@ public class CropImage extends Activity { hv.setFocus(false); hv.invalidate(); } - + for (int i = 0; i < mHighlightViews.size(); i++) { HighlightView hv = mHighlightViews.get(i); int edge = hv.getHit(event.getX(), event.getY()); @@ -175,13 +175,13 @@ public class CropImage extends Activity { } invalidate(); } - + @Override public boolean onTouchEvent(MotionEvent event) { CropImage cropImage = (CropImage)mContext; if (cropImage.mSaving) return false; - + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (cropImage.mWaitingToPick) { @@ -195,8 +195,8 @@ public class CropImage extends Activity { mMotionHighlightView = hv; mLastX = event.getX(); mLastY = event.getY(); - mMotionHighlightView.setMode(edge == HighlightView.MOVE - ? HighlightView.ModifyMode.Move + mMotionHighlightView.setMode(edge == HighlightView.MOVE + ? HighlightView.ModifyMode.Move : HighlightView.ModifyMode.Grow); break; } @@ -251,17 +251,17 @@ public class CropImage extends Activity { break; case MotionEvent.ACTION_MOVE: // if we're not zoomed then there's no point in even allowing - // the user to move the image around. This call to center + // the user to move the image around. This call to center // puts it back to the normalized location (with false meaning // don't animate). if (getScale() == 1F) center(true, true, false); break; } - + return true; } - + private void ensureVisible(HighlightView hv) { Rect r = hv.mDrawRect; @@ -277,32 +277,32 @@ public class CropImage extends Activity { if (panDeltaX != 0 || panDeltaY != 0) panBy(panDeltaX, panDeltaY); } - + private void centerBasedOnHighlightView(HighlightView hv) { Rect drawRect = hv.mDrawRect; - + float width = drawRect.width(); float height = drawRect.height(); - + float thisWidth = getWidth(); float thisHeight = getHeight(); - + float z1 = thisWidth / width * .6F; float z2 = thisHeight / height * .6F; - + float zoom = Math.min(z1, z2); zoom = zoom * this.getScale(); zoom = Math.max(1F, zoom); - + if ((Math.abs(zoom - getScale()) / zoom) > .1) { float [] coordinates = new float[] { hv.mCropRect.centerX(), hv.mCropRect.centerY() }; getImageMatrix().mapPoints(coordinates); zoomTo(zoom, coordinates[0], coordinates[1], 300F); } - + ensureVisible(hv); } - + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -310,15 +310,15 @@ public class CropImage extends Activity { mHighlightViews.get(i).draw(canvas); } } - + public HighlightView get(int i) { return mHighlightViews.get(i); } - + public int size() { return mHighlightViews.size(); } - + public void add(HighlightView hv) { mHighlightViews.add(hv); invalidate(); @@ -333,7 +333,7 @@ public class CropImage extends Activity { paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); c.drawRect(0F, 0F, width, height, paint); } - + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -344,6 +344,8 @@ public class CropImage extends Activity { mImageView = (CropImageView) findViewById(R.id.image); + MenuHelper.showStorageToast(this); + try { android.content.Intent intent = getIntent(); Bundle extras = intent.getExtras(); @@ -379,15 +381,16 @@ public class CropImage extends Activity { Uri target = intent.getData(); mAllImages = ImageManager.makeImageList(target, CropImage.this, ImageManager.SORT_ASCENDING); mImage = mAllImages.getImageForUri(target); - - // don't read in really large bitmaps. max out at 1000. - // TODO when saving the resulting bitmap use the decode/crop/encode - // api so we don't lose any resolution - mBitmap = mImage.thumbBitmap(); - if (Config.LOGV) - Log.v(TAG, "thumbBitmap returned " + mBitmap); + if(mImage != null) { + // don't read in really large bitmaps. max out at 1000. + // TODO when saving the resulting bitmap use the decode/crop/encode + // api so we don't lose any resolution + mBitmap = mImage.thumbBitmap(); + if (Config.LOGV) + Log.v(TAG, "thumbBitmap returned " + mBitmap); + } } - + if (mBitmap == null) { finish(); return; @@ -406,7 +409,7 @@ public class CropImage extends Activity { new Thread(new Runnable() { public void run() { final Bitmap b = mImage != null ? mImage.fullSizeBitmap(500) : mBitmap; - if (Config.LOGV) + if (Config.LOGV) Log.v(TAG, "back from mImage.fullSizeBitmap(500) with bitmap of size " + b.getWidth() + " / " + b.getHeight()); mHandler.post(new Runnable() { public void run() { @@ -427,13 +430,13 @@ public class CropImage extends Activity { Log.e(TAG, "Failed to load bitmap", e); finish(); } - + findViewById(R.id.discard).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { finish(); } }); - + findViewById(R.id.save).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { // TODO this code needs to change to use the decode/crop/encode single @@ -461,7 +464,7 @@ public class CropImage extends Activity { c1.drawBitmap(mBitmap, r, new Rect(0, 0, width, height), null); if (mCircleCrop) { - // OK, so what's all this about? + // OK, so what's all this about? // Bitmaps are inherently rectangular but we want to return something // that's basically a circle. So we fill in the area around the circle // with alpha. Note the all important PortDuff.Mode.CLEAR. @@ -532,7 +535,7 @@ public class CropImage extends Activity { } if (outputStream != null) mCroppedImage.compress(mSaveFormat, 75, outputStream); - + } catch (IOException ex) { if (Config.LOGV) Log.v(TAG, "got IOException " + ex); @@ -541,7 +544,7 @@ public class CropImage extends Activity { try { outputStream.close(); } catch (IOException ex) { - + } } } @@ -553,13 +556,13 @@ public class CropImage extends Activity { } else { Bundle extras = new Bundle(); extras.putString("rect", mCrop.getCropRect().toString()); - - // here we decide whether to create a new image or + + // here we decide whether to create a new image or // modify the existing image if (false) { /* // this is the "modify" case - ImageManager.IGetBoolean_cancelable cancelable = + ImageManager.IGetBoolean_cancelable cancelable = mImage.saveImageContents(mCroppedImage, null, null, null, mImage.getDateTaken(), 0, false); boolean didSave = cancelable.get(); extras.putString("thumb1uri", mImage.thumbUri().toString()); @@ -571,11 +574,11 @@ public class CropImage extends Activity { // this is the "new image" case java.io.File oldPath = new java.io.File(mImage.getDataPath()); java.io.File directory = new java.io.File(oldPath.getParent()); - + int x = 0; String fileName = oldPath.getName(); fileName = fileName.substring(0, fileName.lastIndexOf(".")); - + while (true) { x += 1; String candidate = directory.toString() + "/" + fileName + "-" + x + ".jpg"; @@ -585,7 +588,7 @@ public class CropImage extends Activity { if (!exists) break; } - + try { Uri newUri = ImageManager.instance().addImage( CropImage.this, @@ -600,10 +603,10 @@ public class CropImage extends Activity { ImageManager.IAddImage_cancelable cancelable = ImageManager.instance().storeImage( newUri, - CropImage.this, - getContentResolver(), + CropImage.this, + getContentResolver(), 0, // TODO fix this orientation - mCroppedImage, + mCroppedImage, null); cancelable.get(); @@ -611,7 +614,7 @@ public class CropImage extends Activity { (new Intent()).setAction(newUri.toString()) .putExtras(extras)); } catch (Exception ex) { - // basically ignore this or put up + // basically ignore this or put up // some ui saying we failed } } @@ -625,7 +628,7 @@ public class CropImage extends Activity { } }); } - + @Override public void onResume() { super.onResume(); @@ -639,7 +642,7 @@ public class CropImage extends Activity { Matrix mImageMatrix; FaceDetector.Face[] mFaces = new FaceDetector.Face[3]; int mNumFaces; - + private void handleFace(FaceDetector.Face f) { PointF midPoint = new PointF(); @@ -647,58 +650,58 @@ public class CropImage extends Activity { f.getMidPoint(midPoint); midPoint.x *= mScale; midPoint.y *= mScale; - + int midX = (int) midPoint.x; int midY = (int) midPoint.y; - + HighlightView hv = makeHighlightView(); int width = mBitmap.getWidth(); int height = mBitmap.getHeight(); - + Rect imageRect = new Rect(0, 0, width, height); - + RectF faceRect = new RectF(midX, midY, midX, midY); faceRect.inset(-r, -r); if (faceRect.left < 0) faceRect.inset(-faceRect.left, -faceRect.left); - + if (faceRect.top < 0) faceRect.inset(-faceRect.top, -faceRect.top); - + if (faceRect.right > imageRect.right) faceRect.inset(faceRect.right - imageRect.right, faceRect.right - imageRect.right); - + if (faceRect.bottom > imageRect.bottom) faceRect.inset(faceRect.bottom - imageRect.bottom, faceRect.bottom - imageRect.bottom); hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop, mAspectX != 0 && mAspectY != 0); - + if (mUnion == null) { mUnion = new RectF(faceRect); } else { mUnion.union(faceRect); } - mImageView.add(hv); + mImageView.add(hv); } - + private HighlightView makeHighlightView() { return new HighlightView(mImageView); } - + private void makeDefault() { HighlightView hv = makeHighlightView(); int width = mBitmap.getWidth(); int height = mBitmap.getHeight(); - + Rect imageRect = new Rect(0, 0, width, height); // make the default size about 4/5 of the width or height int cropWidth = Math.min(width, height) * 4 / 5; int cropHeight = cropWidth; - + if (mAspectX != 0 && mAspectY != 0) { if (mAspectX > mAspectY) { cropHeight = cropWidth * mAspectY / mAspectX; @@ -711,16 +714,16 @@ public class CropImage extends Activity { int x = (width - cropWidth) / 2; int y = (height - cropHeight) / 2; - + RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight); hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop, mAspectX != 0 && mAspectY != 0); mImageView.add(hv); } - + private Bitmap prepareBitmap() { if (mBitmap == null) return null; - + // scale the image down for faster face detection // 256 pixels wide is enough. if (mBitmap.getWidth() > 256) { @@ -730,13 +733,13 @@ public class CropImage extends Activity { matrix.setScale(mScale, mScale); Bitmap faceBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap .getWidth(), mBitmap.getHeight(), matrix, true); - return faceBitmap; + return faceBitmap; } - + public void run() { mImageMatrix = mImageView.getImageMatrix(); Bitmap faceBitmap = prepareBitmap(); - + mScale = 1.0F / mScale; if (faceBitmap != null && mDoFaceDetection) { FaceDetector detector = new FaceDetector(faceBitmap.getWidth(), @@ -745,8 +748,8 @@ public class CropImage extends Activity { if (Config.LOGV) Log.v(TAG, "numFaces is " + mNumFaces); } - mHandler.post(new Runnable() { - public void run() { + mHandler.post(new Runnable() { + public void run() { mWaitingToPick = mNumFaces > 1; if (mNumFaces > 0) { for (int i = 0; i < mNumFaces; i++) { @@ -767,9 +770,9 @@ public class CropImage extends Activity { Toast t = Toast.makeText(CropImage.this, R.string.multiface_crop_help, Toast.LENGTH_SHORT); t.show(); } - } + } }); - + } }; @@ -780,15 +783,15 @@ public class CropImage extends Activity { if (mAllImages != null) mAllImages.deactivate(); } - + private synchronized void closeProgressDialog() { if (mFaceDetectionDialog != null) { mFaceDetectionDialog.dismiss(); mFaceDetectionDialog = null; - } + } if (mSavingProgressDialog != null) { mSavingProgressDialog.dismiss(); mSavingProgressDialog = null; - } + } } } diff --git a/src/com/android/camera/GalleryPicker.java b/src/com/android/camera/GalleryPicker.java index df9049f..44f8fc2 100644 --- a/src/com/android/camera/GalleryPicker.java +++ b/src/com/android/camera/GalleryPicker.java @@ -62,6 +62,7 @@ import java.util.Map; public class GalleryPicker extends Activity { static private final String TAG = "GalleryPicker"; + private View mNoImagesView; GridView mGridView; Drawable mFrameGalleryMask; Drawable mCellOutline; @@ -107,6 +108,7 @@ public class GalleryPicker extends Activity { setContentView(R.layout.gallerypicker); + mNoImagesView = findViewById(R.id.no_images); mGridView = (GridView) findViewById(R.id.albums); mGridView.setSelector(android.R.color.transparent); @@ -511,14 +513,21 @@ public class GalleryPicker extends Activity { }); t.start(); - // If we just have one folder, open it. (Probably never triggered because we always have - // At least two folders now.) - if (!scanning && mAdapter.mItems.size() <= 1) { - android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI; - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); - finish(); - return; + // If we just have zero or one folder, open it. (We shouldn't have just one folder + // any more, but we can have zero folders.) + mNoImagesView.setVisibility(View.GONE); + if (!scanning) { + int numItems = mAdapter.mItems.size(); + if (numItems == 0) { + mNoImagesView.setVisibility(View.VISIBLE); + } else if (numItems == 1) { + // Not sure we can ever get here any more. + android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI; + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + finish(); + return; + } } } diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java index f8e1658..034c441 100644 --- a/src/com/android/camera/MenuHelper.java +++ b/src/com/android/camera/MenuHelper.java @@ -25,7 +25,9 @@ import android.content.Intent; import android.content.SharedPreferences; import android.media.MediaMetadataRetriever; import android.net.Uri; +import android.os.Environment; import android.os.Handler; +import android.os.StatFs; import android.provider.MediaStore; import android.provider.MediaStore.Images; import android.util.Config; @@ -83,6 +85,9 @@ public class MenuHelper { static public final int MENU_VIDEO_SHARE = 24; static public final int MENU_VIDEO_TOSS = 27; + public static final int NO_STORAGE_ERROR = -1; + public static final int CANNOT_STAT_ERROR = -2; + public interface MenuItemsResult { public void gettingReadyToOpen(Menu menu, ImageManager.IImage image); public void aboutToCall(MenuItem item, ImageManager.IImage image); @@ -300,20 +305,10 @@ public class MenuHelper { } try { - long durationMs = Long.parseLong(retriever.extractMetadata( + int durationMs = Integer.parseInt(retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_DURATION)); - long duration = durationMs / 1000; - long h = duration / 3600; - long m = (duration - h * 3600) / 60; - long s = duration - (h * 3600 + m * 60); - String durationValue; - if (h == 0) { - durationValue = String.format( - activity.getString(R.string.details_ms), m, s); - } else { - durationValue = String.format( - activity.getString(R.string.details_hms), h, m, s); - } + String durationValue = formatDuration( + activity, durationMs); ((TextView)d.findViewById(R.id.details_duration_value)) .setText(durationValue); } catch (NumberFormatException e) { @@ -605,5 +600,55 @@ public class MenuHelper { int keyboard = activity.getResources().getConfiguration().hardKeyboardHidden; flipItem.setEnabled(keyboard != android.content.res.Configuration.HARDKEYBOARDHIDDEN_NO); } + + public static String formatDuration(final Activity activity, int durationMs) { + int duration = durationMs / 1000; + int h = duration / 3600; + int m = (duration - h * 3600) / 60; + int s = duration - (h * 3600 + m * 60); + String durationValue; + if (h == 0) { + durationValue = String.format( + activity.getString(R.string.details_ms), m, s); + } else { + durationValue = String.format( + activity.getString(R.string.details_hms), h, m, s); + } + return durationValue; + } + + + public static void showStorageToast(Activity activity) { + String noStorageText = null; + int remaining = calculatePicturesRemaining(); + + if (remaining == MenuHelper.NO_STORAGE_ERROR) { + noStorageText = activity.getString(R.string.no_storage); + } else if (remaining < 1) { + noStorageText = activity.getString(R.string.not_enough_space); + } + + if (noStorageText != null) { + Toast.makeText(activity, noStorageText, 5000).show(); + } + } + + public static int calculatePicturesRemaining() { + try { + if (!ImageManager.hasStorage()) { + return NO_STORAGE_ERROR; + } else { + String storageDirectory = Environment.getExternalStorageDirectory().toString(); + StatFs stat = new StatFs(storageDirectory); + float remaining = ((float)stat.getAvailableBlocks() * (float)stat.getBlockSize()) / 400000F; + return (int)remaining; + } + } catch (Exception ex) { + // if we can't stat the filesystem then we don't know how many + // pictures are remaining. it might be zero but just leave it + // blank since we really don't know. + return CANNOT_STAT_ERROR; + } + } } diff --git a/src/com/android/camera/MovieView.java b/src/com/android/camera/MovieView.java index 58e80df..b93336c 100644 --- a/src/com/android/camera/MovieView.java +++ b/src/com/android/camera/MovieView.java @@ -18,14 +18,22 @@ package com.android.camera; import android.app.Activity; +import android.app.AlertDialog; +import android.content.ContentValues; +import android.content.DialogInterface; import android.content.Intent; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; import android.content.pm.ActivityInfo; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore; +import android.provider.MediaStore.Video; import android.view.View; import android.widget.MediaController; import android.widget.VideoView; @@ -41,6 +49,7 @@ public class MovieView extends Activity implements MediaPlayer.OnErrorListener, private VideoView mVideoView; private View mProgressView; private boolean mFinishOnCompletion; + private Uri mUri; public MovieView() { } @@ -63,11 +72,11 @@ public class MovieView extends Activity implements MediaPlayer.OnErrorListener, } } mFinishOnCompletion = intent.getBooleanExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true); - Uri uri = intent.getData(); + mUri = intent.getData(); // For streams that we expect to be slow to start up, show a // progress spinner until playback starts. - String scheme = uri.getScheme(); + String scheme = mUri.getScheme(); if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) { mHandler.postDelayed(mPlayingChecker, 250); @@ -77,20 +86,100 @@ public class MovieView extends Activity implements MediaPlayer.OnErrorListener, mVideoView.setOnErrorListener(this); mVideoView.setOnCompletionListener(this); - mVideoView.setVideoURI(uri); + mVideoView.setVideoURI(mUri); mVideoView.setMediaController(new MediaController(this)); mVideoView.requestFocus(); // make the video view handle keys for seeking and pausing Intent i = new Intent(SERVICECMD); i.putExtra(CMDNAME, CMDPAUSE); sendBroadcast(i); + { + final Integer bookmark = getBookmark(); + if (bookmark != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.resume_playing_title); + builder.setMessage(String.format( + getString(R.string.resume_playing_message), + MenuHelper.formatDuration(this, bookmark))); + builder.setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + finish(); + }}); + builder.setPositiveButton(R.string.resume_playing_resume, + new OnClickListener(){ + public void onClick(DialogInterface dialog, int which) { + mVideoView.seekTo(bookmark); + mVideoView.start(); + }}); + builder.setNegativeButton(R.string.resume_playing_restart, new OnClickListener(){ + public void onClick(DialogInterface dialog, int which) { + mVideoView.start(); + }}); + builder.show(); + } else { + mVideoView.start(); + } + } + } - mVideoView.start(); + private Integer getBookmark() { + String scheme = mUri.getScheme(); + if ("content".equalsIgnoreCase(scheme)) { + String[] projection = new String[]{Video.VideoColumns.DURATION, + Video.VideoColumns.BOOKMARK}; + try { + Cursor cursor = getContentResolver().query(mUri, projection, null, null, null); + if (cursor != null) { + try { + if ( cursor.moveToFirst() ) { + int duration = getCursorInteger(cursor, 0); + int bookmark = getCursorInteger(cursor, 1); + final int ONE_MINUTE = 60 * 1000; + final int TWO_MINUTES = 2 * ONE_MINUTE; + final int FIVE_MINUTES = 5 * ONE_MINUTE; + if ((bookmark < TWO_MINUTES) + || (duration < FIVE_MINUTES) + || (bookmark > (duration - ONE_MINUTE))) { + return null; + } + + return new Integer(bookmark); + } + } finally { + cursor.close(); + } + } + } catch (SQLiteException e) { + // ignore + } + } + return null; + } + + private int getCursorInteger(Cursor cursor, int index) { + try { + return cursor.getInt(index); + } catch (SQLiteException e) { + return 0; + } catch (NumberFormatException e) { + return 0; + } + + } + + private void setBookmark(int bookmark) { + String scheme = mUri.getScheme(); + if ("content".equalsIgnoreCase(scheme)) { + ContentValues values = new ContentValues(); + values.put(Video.VideoColumns.BOOKMARK, Integer.toString(bookmark)); + getContentResolver().update(mUri, values, null, null); + } } @Override public void onPause() { mHandler.removeCallbacksAndMessages(null); + setBookmark(mVideoView.getCurrentPosition()); super.onPause(); } diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index 9da1f49..9bfb860 100644 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -67,8 +67,8 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa private static final boolean DEBUG = true; private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = DEBUG && false; private static final boolean DEBUG_DO_NOT_REUSE_MEDIA_RECORDER = DEBUG && true; + private static final boolean DEBUG_LOG_APP_LIFECYCLE = DEBUG && false; - private static final int KEEP = 2; private static final int CLEAR_SCREEN_DELAY = 4; private static final int UPDATE_RECORD_TIME = 5; @@ -86,7 +86,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa public static final int MENU_SAVE_SELECT_VIDEO = 36; public static final int MENU_SAVE_NEW_VIDEO = 37; - Toast mToast; SharedPreferences mPreferences; private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f; @@ -98,9 +97,14 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa private MediaRecorder mMediaRecorder; private boolean mMediaRecorderRecording = false; - private boolean mNeedToDeletePartialRecording; private boolean mNeedToRegisterRecording; private long mRecordingStartTime; + // The video file that the hardware camera is about to record into + // (or is recording into.) + private String mCameraVideoFilename; + + // The video file that has already been recorded, and that is being + // examined by the user. private String mCurrentVideoFilename; private Uri mCurrentVideoUri; private ContentValues mCurrentVideoValues; @@ -123,26 +127,11 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa private Handler mHandler = new MainHandler(); - private void cancelSavingNotification() { - if (mToast != null) { - mToast.cancel(); - mToast = null; - } - } - /** This Handler is used to post message back onto the main thread of the application */ private class MainHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { - case KEEP: { - keep(); - - if (msg.obj != null) { - mHandler.post((Runnable)msg.obj); - } - break; - } case CLEAR_SCREEN_DELAY: { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -209,15 +198,12 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); } - private void postAfterKeep(final Runnable r) { - Message msg = mHandler.obtainMessage(KEEP); - msg.obj = r; - msg.sendToTarget(); - } - /** Called with the activity is first created. */ @Override public void onCreate(Bundle icicle) { + if (DEBUG_LOG_APP_LIFECYCLE) { + Log.v(TAG, "onCreate " + this.hashCode()); + } super.onCreate(icicle); mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); @@ -248,7 +234,8 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa mPostPictureAlert = findViewById(R.id.post_picture_panel); int[] ids = new int[]{R.id.play, R.id.share, R.id.discard, - R.id.capture, R.id.cancel, R.id.accept}; + R.id.capture, R.id.cancel, R.id.accept, R.id.mode_indicator, + R.id.recording_indicator}; for (int id : ids) { findViewById(id).setOnClickListener(this); } @@ -261,6 +248,9 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa @Override public void onStart() { + if (DEBUG_LOG_APP_LIFECYCLE) { + Log.v(TAG, "onStart " + this.hashCode()); + } super.onStart(); final View hintView = findViewById(R.id.hint_toast); @@ -304,7 +294,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa public void onClick(View v) { switch (v.getId()) { case R.id.capture: - doDiscardCurrentVideo(); + doStartCaptureMode(); break; case R.id.accept: @@ -316,7 +306,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa break; case R.id.discard: { - doDiscardCurrentVideo(); + discardCurrentVideoAndStartPreview(); break; } @@ -338,10 +328,30 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa doPlayCurrentVideo(); break; } + + case R.id.mode_indicator: + if (mVideoFrame.getVisibility() == View.VISIBLE) { + doStartCaptureMode(); + } + startVideoRecording(); + break; + + case R.id.recording_indicator: + stopVideoRecordingAndDisplayDialog(); + break; + } + } + + private void doStartCaptureMode() { + if (isPickIntent()) { + discardCurrentVideoAndStartPreview(); + } else { + hideVideoFrameAndStartPreview(); } } private void doPlayCurrentVideo() { + Log.e(TAG, "Playing current video: " + mCurrentVideoUri); Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); try { startActivity(intent); @@ -350,7 +360,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } } - private void doDiscardCurrentVideo() { + private void discardCurrentVideoAndStartPreview() { deleteCurrentVideo(); hideVideoFrameAndStartPreview(); } @@ -369,6 +379,9 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa @Override public void onResume() { + if (DEBUG_LOG_APP_LIFECYCLE) { + Log.v(TAG, "onResume " + this.hashCode()); + } super.onResume(); mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); @@ -393,18 +406,22 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa @Override public void onStop() { - Log.v(TAG, "onStop"); + if (DEBUG_LOG_APP_LIFECYCLE) { + Log.v(TAG, "onStop " + this.hashCode()); + } stopVideoRecording(); - keep(); mHandler.removeMessages(CLEAR_SCREEN_DELAY); super.onStop(); } @Override protected void onPause() { - Log.v(TAG, "onPause"); + if (DEBUG_LOG_APP_LIFECYCLE) { + Log.v(TAG, "onPause " + this.hashCode()); + } + super.onPause(); + stopVideoRecording(); - keep(); hidePostPictureAlert(); mPausing = true; @@ -413,8 +430,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa unregisterReceiver(mReceiver); mDidRegister = false; } - - super.onPause(); + mBlackout.setVisibility(View.VISIBLE); } @Override @@ -481,14 +497,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } } - void keep() { - cancelSavingNotification(); - }; - - void toss() { - cancelSavingNotification(); - }; - @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); @@ -500,7 +508,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true); - return true; } @@ -531,11 +538,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa R.string.camera_gallery_photos_text).setOnMenuItemClickListener( new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - postAfterKeep(new Runnable() { - public void run() { - gotoGallery(); - } - }); + gotoGallery(); return true; } }); @@ -593,7 +596,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } mMediaRecorder = new MediaRecorder(); - mNeedToDeletePartialRecording = true; mNeedToRegisterRecording = false; if (DEBUG_SUPPRESS_AUDIO_RECORDING) { @@ -603,9 +605,8 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - Log.v(TAG, "before setOutputFile"); createVideoPath(); - mMediaRecorder.setOutputFile(mCurrentVideoFilename); + mMediaRecorder.setOutputFile(mCameraVideoFilename); boolean videoQualityHigh = getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY, CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); @@ -631,12 +632,11 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa if (!DEBUG_SUPPRESS_AUDIO_RECORDING) { mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); } - Log.v(TAG, "before setPreviewDisplay"); mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); try { mMediaRecorder.prepare(); } catch (IOException exception) { - Log.e(TAG, "prepare failed for " + mCurrentVideoFilename); + Log.e(TAG, "prepare failed for " + mCameraVideoFilename); releaseMediaRecorder(); // TODO: add more exception handling logic here return; @@ -661,7 +661,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa try { mMediaRecorder.prepare(); } catch (IOException exception) { - Log.e(TAG, "prepare failed for " + mCurrentVideoFilename); + Log.e(TAG, "prepare failed for " + mCameraVideoFilename); releaseMediaRecorder(); // TODO: add more exception handling logic here } @@ -698,25 +698,22 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa values.put(Video.Media.DATE_TAKEN, dateTaken); values.put(Video.Media.MIME_TYPE, "video/3gpp"); values.put(Video.Media.DATA, filename); - mCurrentVideoFilename = filename; + mCameraVideoFilename = filename; + Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename); mCurrentVideoValues = values; - mCurrentVideoUri = null; } private void registerVideo() { Uri videoTable = Uri.parse("content://media/external/video/media"); mCurrentVideoUri = mContentResolver.insert(videoTable, mCurrentVideoValues); + Log.v(TAG, "Current video URI: " + mCurrentVideoUri); mCurrentVideoValues = null; } private void deleteCurrentVideo() { if (mCurrentVideoFilename != null) { - Log.v(TAG, "Deleting stub video " + mCurrentVideoFilename); - File f = new File(mCurrentVideoFilename); - if (! f.delete()) { - Log.v(TAG, "Could not delete " + mCurrentVideoFilename); - } + deleteVideoFile(mCurrentVideoFilename); mCurrentVideoFilename = null; } if (mCurrentVideoUri != null) { @@ -725,6 +722,14 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } } + private void deleteVideoFile(String fileName) { + Log.v(TAG, "Deleting video " + fileName); + File f = new File(fileName); + if (! f.delete()) { + Log.v(TAG, "Could not delete " + fileName); + } + } + private void addBaseMenuItems(Menu menu) { MenuHelper.addSwitchModeMenuItem(menu, this, false); { @@ -817,7 +822,9 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa if (mMediaRecorderRecording || mMediaRecorder != null) { if (mMediaRecorderRecording) { mMediaRecorder.stop(); - mNeedToDeletePartialRecording = false; + mCurrentVideoFilename = mCameraVideoFilename; + Log.v(TAG, "Setting current video filename: " + mCurrentVideoFilename); + mCameraVideoFilename = null; mNeedToRegisterRecording = true; mMediaRecorderRecording = false; } @@ -831,9 +838,8 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa registerVideo(); mNeedToRegisterRecording = false; } - if (mNeedToDeletePartialRecording){ - deleteCurrentVideo(); - mNeedToDeletePartialRecording = false; + if (mCameraVideoFilename != null){ + deleteVideoFile(mCameraVideoFilename); } } |