diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-02-10 15:44:04 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-02-10 15:44:04 -0800 |
commit | dee42a6e53cb1d0c7194b0cb92028cca353d7c5c (patch) | |
tree | 4fe26dbb188bf5ec71455fa71ce93470342269a0 /src | |
parent | 8250896ab37545f4499da79f54c19af2e5018b80 (diff) | |
download | LegacyCamera-dee42a6e53cb1d0c7194b0cb92028cca353d7c5c.zip LegacyCamera-dee42a6e53cb1d0c7194b0cb92028cca353d7c5c.tar.gz LegacyCamera-dee42a6e53cb1d0c7194b0cb92028cca353d7c5c.tar.bz2 |
auto import from //branches/cupcake/...@130745
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/camera/Camera.java | 538 | ||||
-rw-r--r-- | src/com/android/camera/CropImage.java | 1 | ||||
-rw-r--r-- | src/com/android/camera/GalleryPicker.java | 148 | ||||
-rw-r--r-- | src/com/android/camera/ImageGallery2.java | 34 | ||||
-rwxr-xr-x | src/com/android/camera/ImageManager.java | 128 | ||||
-rw-r--r-- | src/com/android/camera/MenuHelper.java | 75 | ||||
-rw-r--r-- | src/com/android/camera/MovieView.java | 34 | ||||
-rw-r--r-- | src/com/android/camera/SlideShow.java | 2 | ||||
-rw-r--r-- | src/com/android/camera/VideoCamera.java | 283 | ||||
-rw-r--r-- | src/com/android/camera/ViewImage.java | 174 |
10 files changed, 880 insertions, 537 deletions
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index 11b1ccd..a6775ae 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -34,6 +34,7 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; @@ -41,6 +42,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.TransitionDrawable; import android.hardware.Camera.PictureCallback; import android.hardware.Camera.Size; import android.location.Location; @@ -73,6 +75,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.MenuItem.OnMenuItemClickListener; import android.view.ViewGroup.LayoutParams; +import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageView; @@ -86,6 +89,8 @@ public class Camera extends Activity implements View.OnClickListener, private static final String TAG = "camera"; private static final boolean DEBUG = false; + private static final boolean DEBUG_TIME_OPERATIONS = DEBUG && false; + private static final boolean DEBUG_FAKE_GPS_LOCATION = DEBUG && false; private static final int CROP_MSG = 1; private static final int KEEP = 2; @@ -124,6 +129,7 @@ public class Camera extends Activity implements View.OnClickListener, private static final String sTempCropFilename = "crop-temp"; private android.hardware.Camera mCameraDevice; + private android.hardware.Camera.Parameters mParameters; private VideoPreview mSurfaceView; private SurfaceHolder mSurfaceHolder = null; private ImageView mBlackout = null; @@ -152,6 +158,12 @@ public class Camera extends Activity implements View.OnClickListener, private boolean mMenuSelectionMade; private ImageView mLastPictureButton; + private LayerDrawable mVignette; + private Animation mShowLastPictureButtonAnimation = new AlphaAnimation(0F, 1F); + private boolean mShouldShowLastPictureButton; + private TransitionDrawable mThumbnailTransition; + private Drawable[] mThumbnails; + private boolean mShouldTransitionThumbnails; private Uri mLastPictureUri; private LocationManager mLocationManager = null; @@ -170,6 +182,9 @@ public class Camera extends Activity implements View.OnClickListener, private boolean mKeepAndRestartPreview; + private View mPostCaptureAlert; + + private Handler mHandler = new MainHandler(); private ProgressDialog mSavingProgress; @@ -217,6 +232,7 @@ public class Camera extends Activity implements View.OnClickListener, mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 100); } else if (mStatus == SNAPSHOT_COMPLETED){ mCaptureObject.dismissFreezeFrame(true); + hidePostCaptureAlert(); } break; } @@ -244,9 +260,11 @@ public class Camera extends Activity implements View.OnClickListener, // TODO put up a "please wait" message // TODO also listen for the media scanner finished message showStorageToast(); - } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { + } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED) || + action.equals(Intent.ACTION_MEDIA_CHECKING)) { // SD card unavailable - showStorageToast(); + mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR; + showStorageToast(mPicturesRemaining); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { Toast.makeText(Camera.this, getResources().getString(R.string.wait), 5000); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { @@ -298,7 +316,7 @@ public class Camera extends Activity implements View.OnClickListener, private final class ShutterCallback implements android.hardware.Camera.ShutterCallback { public void onShutter() { - if (DEBUG) { + if (DEBUG_TIME_OPERATIONS) { long now = System.currentTimeMillis(); Log.v(TAG, "********** Total shutter lag " + (now - mShutterPressTime) + " ms"); } @@ -314,7 +332,7 @@ public class Camera extends Activity implements View.OnClickListener, if (Config.LOGV) Log.v(TAG, "got RawPictureCallback..."); mRawPictureCallbackTime = System.currentTimeMillis(); - mBlackout.setVisibility(View.INVISIBLE); + mBlackout.setVisibility(View.GONE); } }; @@ -428,7 +446,7 @@ public class Camera extends Activity implements View.OnClickListener, private void storeImage(byte[] data, Location loc) { try { - if (DEBUG) { + if (DEBUG_TIME_OPERATIONS) { startTiming(); } long dateTaken = System.currentTimeMillis(); @@ -455,7 +473,7 @@ public class Camera extends Activity implements View.OnClickListener, mAddImageCancelable = null; } - if (DEBUG) { + if (DEBUG_TIME_OPERATIONS) { stopTiming(); Log.d(TAG, "Storing image took " + (mWallTimeEnd - mWallTimeStart) + " ms. " + "Thread time was " + ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + @@ -472,19 +490,19 @@ public class Camera extends Activity implements View.OnClickListener, if (!captureOnly) { storeImage(data, loc); sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", mLastContentUri)); - setLastPictureThumb(data); + setLastPictureThumb(data, mCaptureObject.getLastCaptureUri()); dismissFreezeFrame(true); } else { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 4; - if (DEBUG) { + if (DEBUG_TIME_OPERATIONS) { startTiming(); } mCaptureOnlyBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); - if (DEBUG) { + if (DEBUG_TIME_OPERATIONS) { stopTiming(); Log.d(TAG, "Decoded mCaptureOnly bitmap (" + mCaptureOnlyBitmap.getWidth() + "x" + mCaptureOnlyBitmap.getHeight() + " ) in " + @@ -492,56 +510,16 @@ public class Camera extends Activity implements View.OnClickListener, ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + " ms."); } - openOptionsMenu(); + showPostCaptureAlert(); + cancelAutomaticPreviewRestart(); } - mCapturing = false; if (mPausing) { closeCamera(); } } - private void setLastPictureThumb(byte[] data) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = 16; - - if (DEBUG) { - startTiming(); - } - - Bitmap lastPictureThumb = BitmapFactory.decodeByteArray(data, 0, data.length, options); - - if (DEBUG) { - stopTiming(); - Log.d(TAG, "Decoded lastPictureThumb bitmap (" + lastPictureThumb.getWidth() + - "x" + lastPictureThumb.getHeight() + " ) in " + - (mWallTimeEnd - mWallTimeStart) + " ms. Thread time was " + - ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + " ms."); - } - - final int PADDING_WIDTH = 2; - final int PADDING_HEIGHT = 2; - LayoutParams layoutParams = mLastPictureButton.getLayoutParams(); - // Make the mini-thumbnail size smaller than the button size so that the image corners - // don't peek out from the rounded corners of the frame_thumbnail graphic: - final int miniThumbWidth = layoutParams.width - 2 * PADDING_WIDTH; - final int miniThumbHeight = layoutParams.height - 2 * PADDING_HEIGHT; - - lastPictureThumb = ImageManager.extractMiniThumb(lastPictureThumb, - miniThumbWidth, miniThumbHeight); - - Drawable[] layers = new Drawable[2]; - layers[0] = new BitmapDrawable(lastPictureThumb); - layers[1] = getResources().getDrawable(R.drawable.frame_thumbnail); - LayerDrawable layerDrawable = new LayerDrawable(layers); - layerDrawable.setLayerInset(0, PADDING_WIDTH, PADDING_HEIGHT, - PADDING_WIDTH, PADDING_HEIGHT); - mLastPictureButton.setImageDrawable(layerDrawable); - mLastPictureButton.setVisibility(View.VISIBLE); - mLastPictureUri = mCaptureObject.getLastCaptureUri(); - } - /* * Tells the image capture thread to abort the capture of the * current image. @@ -589,44 +567,63 @@ public class Camera extends Activity implements View.OnClickListener, Boolean recordLocation = mPreferences.getBoolean("pref_camera_recordlocation_key", false); Location loc = recordLocation ? getCurrentLocation() : null; - android.hardware.Camera.Parameters parameters = mCameraDevice.getParameters(); // Quality 75 has visible artifacts, and quality 90 looks great but the files begin to // get large. 85 is a good compromise between the two. - parameters.set("jpeg-quality", 85); - parameters.set("rotation", latchedOrientation); - - parameters.remove("gps-latitude"); - parameters.remove("gps-longitude"); - parameters.remove("gps-altitude"); - parameters.remove("gps-timestamp"); - - if (loc != null) { + mParameters.set("jpeg-quality", 85); + mParameters.set("rotation", latchedOrientation); + + mParameters.remove("gps-latitude"); + mParameters.remove("gps-longitude"); + mParameters.remove("gps-altitude"); + mParameters.remove("gps-timestamp"); + + if (DEBUG_FAKE_GPS_LOCATION) { + // Google London office, having trouble encoding longitude + + if (false) { + // This fails: + mParameters.set("gps-latitude", "51.49473309516907"); + mParameters.set("gps-longitude", "-0.14598190784454346"); + mParameters.set("gps-altitude", "71.0"); // meters + mParameters.set("gps-timestamp", "1233744883"); + } else { + // This works OK: + mParameters.set("gps-latitude", "51.49473309516907"); + mParameters.set("gps-longitude", "-1.0"); + mParameters.set("gps-altitude", "71.0"); // meters + mParameters.set("gps-timestamp", "1233744883"); + } + } else if (loc != null) { double lat = loc.getLatitude(); double lon = loc.getLongitude(); + boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d); - if (lat != 0D && lon != 0d) { - parameters.set("gps-latitude", String.valueOf(lat)); - parameters.set("gps-longitude", String.valueOf(lon)); + if (hasLatLon) { + String latString = String.valueOf(lat); + String lonString = String.valueOf(lon); + mParameters.set("gps-latitude", latString); + mParameters.set("gps-longitude", lonString); if (loc.hasAltitude()) - parameters.set("gps-altitude", String.valueOf(loc.getAltitude())); + mParameters.set("gps-altitude", String.valueOf(loc.getAltitude())); if (loc.getTime() != 0) { // Location.getTime() is UTC in milliseconds. // gps-timestamp is UTC in seconds. long utcTimeSeconds = loc.getTime() / 1000; - parameters.set("gps-timestamp", String.valueOf(utcTimeSeconds)); + mParameters.set("gps-timestamp", String.valueOf(utcTimeSeconds)); } } else { loc = null; } } - Size pictureSize = parameters.getPictureSize(); + + Size pictureSize = mParameters.getPictureSize(); // resize the SurfaceView to the aspect-ratio of the still image // and so that we can see the full image that was taken mSurfaceView.setAspectRatio(pictureSize.width, pictureSize.height); - mCameraDevice.setParameters(parameters); + mCameraDevice.setParameters(mParameters); mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, new JpegPictureCallback(loc)); @@ -653,9 +650,9 @@ public class Camera extends Activity implements View.OnClickListener, // Don't check the filesystem here, we can't afford the latency. Instead, check the // cached value which was calculated when the preview was restarted. - if (DEBUG) mShutterPressTime = System.currentTimeMillis(); + if (DEBUG_TIME_OPERATIONS) mShutterPressTime = System.currentTimeMillis(); if (mPicturesRemaining < 1) { - showStorageToast(); + showStorageToast(mPicturesRemaining); return; } @@ -672,6 +669,52 @@ public class Camera extends Activity implements View.OnClickListener, } } + private void setLastPictureThumb(byte[] data, Uri uri) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = 16; + + Bitmap lastPictureThumb = BitmapFactory.decodeByteArray(data, 0, data.length, options); + + setLastPictureThumb(lastPictureThumb, uri); + } + + private void setLastPictureThumb(Bitmap lastPictureThumb, Uri uri) { + + final int PADDING_WIDTH = 2; + final int PADDING_HEIGHT = 2; + LayoutParams layoutParams = mLastPictureButton.getLayoutParams(); + // Make the mini-thumbnail size smaller than the button size so that the image corners + // don't peek out from the rounded corners of the frame_thumbnail graphic: + final int miniThumbWidth = layoutParams.width - 2 * PADDING_WIDTH; + final int miniThumbHeight = layoutParams.height - 2 * PADDING_HEIGHT; + + lastPictureThumb = ImageManager.extractMiniThumb(lastPictureThumb, + miniThumbWidth, miniThumbHeight); + + Drawable[] vignetteLayers = new Drawable[2]; + vignetteLayers[1] = getResources().getDrawable(R.drawable.frame_thumbnail); + if (mThumbnails == null) { + mThumbnails = new Drawable[2]; + mThumbnails[1] = new BitmapDrawable(lastPictureThumb); + vignetteLayers[0] = mThumbnails[1]; + } else { + mThumbnails[0] = mThumbnails[1]; + mThumbnails[1] = new BitmapDrawable(lastPictureThumb); + mThumbnailTransition = new TransitionDrawable(mThumbnails); + mShouldTransitionThumbnails = true; + vignetteLayers[0] = mThumbnailTransition; + } + + mVignette = new LayerDrawable(vignetteLayers); + mVignette.setLayerInset(0, PADDING_WIDTH, PADDING_HEIGHT, + PADDING_WIDTH, PADDING_HEIGHT); + mLastPictureButton.setImageDrawable(mVignette); + + if (mLastPictureButton.getVisibility() != View.VISIBLE) { + mShouldShowLastPictureButton = true; + } + mLastPictureUri = uri; + } static private String createName(long dateTaken) { return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); @@ -741,10 +784,27 @@ public class Camera extends Activity implements View.OnClickListener, mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); mLastPictureButton = (ImageView) findViewById(R.id.last_picture_button); - mLastPictureButton.setOnClickListener(this); - mLastPictureButton.setVisibility(View.INVISIBLE); + if (!isImageCaptureIntent()) { + ImageManager.IImageList images = ImageManager.instance().allImages( + this, + getContentResolver(), + ImageManager.DataLocation.ALL, + ImageManager.INCLUDE_IMAGES, + ImageManager.SORT_DESCENDING, + ImageManager.CAMERA_IMAGE_BUCKET_ID); + ImageManager.IImage lastPicture = + images.isEmpty() ? null : images.getImageAt(0); + mLastPictureButton.setOnClickListener(this); + if (lastPicture == null) { + mLastPictureButton.setVisibility(View.GONE); + } else { + Bitmap miniThumb = lastPicture.miniThumbBitmap(); + setLastPictureThumb(miniThumb, lastPicture.fullSizeImageUri()); + } + images.deactivate(); + } - mShutterButton = (ShutterButton) findViewById(R.id.mode_indicator); + mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); mShutterButton.setOnShutterButtonListener(this); try { @@ -773,42 +833,22 @@ public class Camera extends Activity implements View.OnClickListener, mFocusBlinkAnimation = AnimationUtils.loadAnimation(this, R.anim.auto_focus_blink); mFocusBlinkAnimation.setRepeatCount(Animation.INFINITE); mFocusBlinkAnimation.setRepeatMode(Animation.REVERSE); + + mPostCaptureAlert = findViewById(R.id.post_picture_panel); } @Override public void onStart() { super.onStart(); - final View hintView = findViewById(R.id.hint_toast); - if (hintView != null) - hintView.setVisibility(View.GONE); - Thread t = new Thread(new Runnable() { public void run() { final boolean storageOK = calculatePicturesRemaining() > 0; - if (hintView == null) - return; - if (storageOK) { + if (!storageOK) { mHandler.post(new Runnable() { public void run() { - hintView.setVisibility(View.VISIBLE); - } - }); - mHandler.postDelayed(new Runnable() { - public void run() { - Animation a = new android.view.animation.AlphaAnimation(1F, 0F); - a.setDuration(500); - a.startNow(); - hintView.setAnimation(a); - hintView.setVisibility(View.GONE); - } - }, 3000); - } else { - mHandler.post(new Runnable() { - public void run() { - hintView.setVisibility(View.GONE); - showStorageToast(); + showStorageToast(mPicturesRemaining); } }); } @@ -822,12 +862,124 @@ public class Camera extends Activity implements View.OnClickListener, case R.id.last_picture_button: viewLastImage(); break; + case R.id.attach: + doAttach(); + break; + case R.id.cancel: + doCancel(); } } + private void doAttach() { + Bitmap bitmap = mImageCapture.getLastBitmap(); + mCaptureObject.setDone(true); + + String cropValue = null; + Uri saveUri = null; + + Bundle myExtras = getIntent().getExtras(); + if (myExtras != null) { + saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); + cropValue = myExtras.getString("crop"); + } + + + if (cropValue == null) { + /* + * First handle the no crop case -- just return the value. If the caller + * specifies a "save uri" then write the data to it's stream. Otherwise, + * pass back a scaled down version of the bitmap directly in the extras. + */ + if (saveUri != null) { + OutputStream outputStream = null; + try { + outputStream = mContentResolver.openOutputStream(saveUri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream); + outputStream.close(); + + setResult(RESULT_OK); + finish(); + } catch (IOException ex) { + // + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ex) { + + } + } + } + } else { + float scale = .5F; + Matrix m = new Matrix(); + m.setScale(scale, scale); + + bitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), + bitmap.getHeight(), + m, true); + + setResult(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap)); + finish(); + } + } + else { + /* + * Save the image to a temp file and invoke the cropper + */ + Uri tempUri = null; + FileOutputStream tempStream = null; + try { + File path = getFileStreamPath(sTempCropFilename); + path.delete(); + tempStream = openFileOutput(sTempCropFilename, 0); + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream); + tempStream.close(); + tempUri = Uri.fromFile(path); + } catch (FileNotFoundException ex) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } catch (IOException ex) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } finally { + if (tempStream != null) { + try { + tempStream.close(); + } catch (IOException ex) { + + } + } + } + + Bundle newExtras = new Bundle(); + if (cropValue.equals("circle")) + newExtras.putString("circleCrop", "true"); + if (saveUri != null) + newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri); + else + newExtras.putBoolean("return-data", true); + + Intent cropIntent = new Intent(); + cropIntent.setClass(Camera.this, CropImage.class); + cropIntent.setData(tempUri); + cropIntent.putExtras(newExtras); + + startActivityForResult(cropIntent, CROP_MSG); + } + } + + private void doCancel() { + setResult(RESULT_CANCELED, new Intent()); + finish(); + } + public void onShutterButtonFocus(ShutterButton button, boolean pressed) { switch (button.getId()) { - case R.id.mode_indicator: + case R.id.shutter_button: doFocus(pressed); break; } @@ -835,7 +987,7 @@ public class Camera extends Activity implements View.OnClickListener, public void onShutterButtonClick(ShutterButton button) { switch (button.getId()) { - case R.id.mode_indicator: + case R.id.shutter_button: doSnap(false); break; } @@ -845,6 +997,10 @@ public class Camera extends Activity implements View.OnClickListener, MenuHelper.showStorageToast(this); } + private void showStorageToast(int remainingPictures) { + MenuHelper.showStorageToast(this, remainingPictures); + } + @Override public void onResume() { super.onResume(); @@ -858,6 +1014,7 @@ public class Camera extends Activity implements View.OnClickListener, intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); + intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING); intentFilter.addDataScheme("file"); registerReceiver(mReceiver, intentFilter); mDidRegister = true; @@ -878,14 +1035,14 @@ public class Camera extends Activity implements View.OnClickListener, mFocusToneGenerator = null; } - mBlackout.setVisibility(View.INVISIBLE); + mBlackout.setVisibility(View.GONE); if (mLastPictureUri != null) { IImageList list = ImageManager.makeImageList(mLastPictureUri, this, ImageManager.SORT_ASCENDING); if (list.getImageForUri(mLastPictureUri) == null) { mLastPictureUri = null; - mLastPictureButton.setVisibility(View.INVISIBLE); + mLastPictureButton.setVisibility(View.GONE); } list.deactivate(); } @@ -996,7 +1153,7 @@ public class Camera extends Activity implements View.OnClickListener, switch (keyCode) { case KeyEvent.KEYCODE_BACK: - if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) { + if (mStatus == SNAPSHOT_IN_PROGRESS) { // ignore backs while we're taking a picture return true; } @@ -1011,6 +1168,18 @@ public class Camera extends Activity implements View.OnClickListener, doSnap(false); } return true; + case KeyEvent.KEYCODE_DPAD_CENTER: + // If we get a dpad center event without any focused view, move the + // focus to the shutter button and press it. + if (event.getRepeatCount() == 0) { + if (mShutterButton.isInTouchMode()) { + mShutterButton.requestFocusFromTouch(); + } else { + mShutterButton.requestFocus(); + } + mShutterButton.setPressed(true); + } + return true; } return super.onKeyDown(keyCode, event); @@ -1053,6 +1222,7 @@ public class Camera extends Activity implements View.OnClickListener, private void doFocus(boolean pressed) { if (pressed) { mIsFocusButtonPressed = true; + mCaptureOnFocus = false; if (mPreviewing) { autoFocus(); } else if (mCaptureObject != null) { @@ -1113,6 +1283,17 @@ public class Camera extends Activity implements View.OnClickListener, // TODO: The best longterm solution is to write a reserve file of maximum JPEG size, always // let the user take a picture, and delete that file if needed to save the new photo. calculatePicturesRemaining(); + + if (mShouldShowLastPictureButton) { + mShouldShowLastPictureButton = false; + mLastPictureButton.setVisibility(View.VISIBLE); + Animation a = mShowLastPictureButtonAnimation; + a.setDuration(500); + mLastPictureButton.setAnimation(a); + } else if (mShouldTransitionThumbnails) { + mShouldTransitionThumbnails = false; + mThumbnailTransition.reverseTransition(500); + } } private void setViewFinder(int w, int h, boolean startPreview) { @@ -1169,10 +1350,10 @@ public class Camera extends Activity implements View.OnClickListener, // request the preview size, the hardware may not honor it, // if we depended on it we would have to query the size again - android.hardware.Camera.Parameters p = mCameraDevice.getParameters(); - p.setPreviewSize(w, h); + mParameters = mCameraDevice.getParameters(); + mParameters.setPreviewSize(w, h); try { - mCameraDevice.setParameters(p); + mCameraDevice.setParameters(mParameters); } catch (IllegalArgumentException e) { // Ignore this error, it happens in the simulator. } @@ -1266,6 +1447,7 @@ public class Camera extends Activity implements View.OnClickListener, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true); intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true); + intent.putExtra("com.android.camera.ReviewMode", true); try { startActivity(intent); @@ -1374,8 +1556,7 @@ public class Camera extends Activity implements View.OnClickListener, public boolean onMenuOpened(int featureId, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL) { if (mStatus == SNAPSHOT_IN_PROGRESS) { - mKeepAndRestartPreview = false; - mHandler.removeMessages(RESTART_PREVIEW); + cancelAutomaticPreviewRestart(); mMenuSelectionMade = false; } } @@ -1408,127 +1589,43 @@ public class Camera extends Activity implements View.OnClickListener, return true; } + private void cancelAutomaticPreviewRestart() { + mKeepAndRestartPreview = false; + mHandler.removeMessages(RESTART_PREVIEW); + } + private boolean isImageCaptureIntent() { String action = getIntent().getAction(); return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)); } + private void showPostCaptureAlert() { + boolean isPick = isImageCaptureIntent(); + int pickVisible = isPick ? View.VISIBLE : View.GONE; + mPostCaptureAlert.setVisibility(pickVisible); + if (isPick) { + int[] pickIds = {R.id.attach, R.id.cancel}; + for(int id : pickIds) { + View view = mPostCaptureAlert.findViewById(id); + view.setOnClickListener(this); + Animation animation = new AlphaAnimation(0F, 1F); + animation.setDuration(500); + view.setAnimation(animation); + } + } + } + + private void hidePostCaptureAlert() { + mPostCaptureAlert.setVisibility(View.GONE); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); if (isImageCaptureIntent()) { - menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_SELECT_PHOTOS , 0, R.string.camera_selectphoto).setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - Bitmap bitmap = mImageCapture.getLastBitmap(); - mCaptureObject.setDone(true); - - String cropValue = null; - Uri saveUri = null; - - Bundle myExtras = getIntent().getExtras(); - if (myExtras != null) { - saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); - cropValue = myExtras.getString("crop"); - } - - - if (cropValue == null) { - /* - * First handle the no crop case -- just return the value. If the caller - * specifies a "save uri" then write the data to it's stream. Otherwise, - * pass back a scaled down version of the bitmap directly in the extras. - */ - if (saveUri != null) { - OutputStream outputStream = null; - try { - outputStream = mContentResolver.openOutputStream(saveUri); - bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream); - outputStream.close(); - - setResult(RESULT_OK); - finish(); - } catch (IOException ex) { - // - } finally { - if (outputStream != null) { - try { - outputStream.close(); - } catch (IOException ex) { - - } - } - } - } else { - float scale = .5F; - Matrix m = new Matrix(); - m.setScale(scale, scale); - - bitmap = Bitmap.createBitmap(bitmap, 0, 0, - bitmap.getWidth(), - bitmap.getHeight(), - m, true); - - setResult(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap)); - finish(); - } - } - else { - /* - * Save the image to a temp file and invoke the cropper - */ - Uri tempUri = null; - FileOutputStream tempStream = null; - try { - File path = getFileStreamPath(sTempCropFilename); - path.delete(); - tempStream = openFileOutput(sTempCropFilename, 0); - bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream); - tempStream.close(); - tempUri = Uri.fromFile(path); - } catch (FileNotFoundException ex) { - setResult(Activity.RESULT_CANCELED); - finish(); - return true; - } catch (IOException ex) { - setResult(Activity.RESULT_CANCELED); - finish(); - return true; - } finally { - if (tempStream != null) { - try { - tempStream.close(); - } catch (IOException ex) { - - } - } - } - - Bundle newExtras = new Bundle(); - if (cropValue.equals("circle")) - newExtras.putString("circleCrop", "true"); - if (saveUri != null) - newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri); - else - newExtras.putBoolean("return-data", true); - - Intent cropIntent = new Intent(); - cropIntent.setClass(Camera.this, CropImage.class); - cropIntent.setData(tempUri); - cropIntent.putExtras(newExtras); - - startActivityForResult(cropIntent, CROP_MSG); - } - return true; - } - }); - - menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_NEW_PHOTO, 0, R.string.camera_takenewphoto).setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - keep(); - return true; - } - }); + // No options menu for attach mode. + return false; } else { addBaseMenuItems(menu); MenuHelper.addImageMenuItems( @@ -1627,10 +1724,5 @@ public class Camera extends Activity implements View.OnClickListener, }); item.setIcon(android.R.drawable.ic_menu_preferences); } - - private void cancelRestartPreviewTimeout() { - mHandler.removeMessages(RESTART_PREVIEW); - } - } diff --git a/src/com/android/camera/CropImage.java b/src/com/android/camera/CropImage.java index 9542ce0..5250b56 100644 --- a/src/com/android/camera/CropImage.java +++ b/src/com/android/camera/CropImage.java @@ -434,6 +434,7 @@ public class CropImage extends Activity { findViewById(R.id.discard).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { + setResult(RESULT_CANCELED); finish(); } }); diff --git a/src/com/android/camera/GalleryPicker.java b/src/com/android/camera/GalleryPicker.java index 44f8fc2..cf883dd 100644 --- a/src/com/android/camera/GalleryPicker.java +++ b/src/com/android/camera/GalleryPicker.java @@ -73,7 +73,6 @@ public class GalleryPicker extends Activity { Dialog mMediaScanningDialog; - MenuItem mFlipItem; SharedPreferences mPrefs; boolean mPausing = false; @@ -96,8 +95,49 @@ public class GalleryPicker extends Activity { true, true); } - mAdapter.notifyDataSetChanged(); - mAdapter.init(!unmounted && !scanning); + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + mAdapter.init(!unmounted && !scanning); + } + + if (!unmounted) { + // Warn the user if space is getting low + Thread t = new Thread(new Runnable() { + public void run() { + + // Check available space only if we are writable + if (ImageManager.hasStorage()) { + String storageDirectory = Environment.getExternalStorageDirectory().toString(); + StatFs stat = new StatFs(storageDirectory); + long remaining = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize(); + if (remaining < LOW_STORAGE_THRESHOLD) { + + mHandler.post(new Runnable() { + public void run() { + Toast.makeText(GalleryPicker.this.getApplicationContext(), + R.string.not_enough_space, 5000).show(); + } + }); + } + } + } + }); + t.start(); + } + + // 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) { + mAdapter.mItems.get(0).launch(this); + finish(); + return; + } + } } @Override @@ -201,6 +241,7 @@ public class GalleryPicker extends Activity { static class Item implements Comparable<Item>{ // The type is also used as the sort order + public final static int TYPE_NONE = -1; public final static int TYPE_ALL_IMAGES = 0; public final static int TYPE_ALL_VIDEOS = 1; public final static int TYPE_CAMERA_IMAGES = 2; @@ -329,14 +370,16 @@ public class GalleryPicker extends Activity { images.deactivate(); notifyDataSetInvalidated(); - // If just one - addBucketIfNotEmpty(Item.TYPE_ALL_IMAGES, null, R.string.all_images); - addBucketIfNotEmpty(Item.TYPE_ALL_VIDEOS, null, R.string.all_videos); + // Conditionally add all-images and all-videos folders. + addBucket(Item.TYPE_ALL_IMAGES, null, + Item.TYPE_CAMERA_IMAGES, cameraBucketId, R.string.all_images); + addBucket(Item.TYPE_ALL_VIDEOS, null, + Item.TYPE_CAMERA_VIDEOS, cameraBucketId, R.string.all_videos); if (cameraBucketId != null) { - addBucketIfNotEmpty(Item.TYPE_CAMERA_IMAGES, cameraBucketId, + addBucket(Item.TYPE_CAMERA_IMAGES, cameraBucketId, R.string.gallery_camera_bucket_name); - addBucketIfNotEmpty(Item.TYPE_CAMERA_VIDEOS, cameraBucketId, + addBucket(Item.TYPE_CAMERA_VIDEOS, cameraBucketId, R.string.gallery_camera_videos_bucket_name); } @@ -401,7 +444,36 @@ public class GalleryPicker extends Activity { mWorkerThread.toBackground(); } - private void addBucketIfNotEmpty(int itemType, String bucketId, int labelId) { + /** + * Add a bucket, but only if it's interesting. + * Interesting means non-empty and not duplicated by the + * corresponding camera bucket. + */ + private void addBucket(int itemType, String bucketId, + int cameraItemType, String cameraBucketId, + int labelId) { + int itemCount = bucketItemCount( + Item.convertItemTypeToIncludedMediaType(itemType), bucketId); + if (itemCount == 0) { + return; // Bucket is empty, so don't show it. + } + int cameraItemCount = 0; + if (cameraBucketId != null) { + cameraItemCount = bucketItemCount( + Item.convertItemTypeToIncludedMediaType(cameraItemType), cameraBucketId); + } + if (cameraItemCount == itemCount) { + return; // Bucket is the same as the camera bucket, so don't show it. + } + mItems.add(new Item(itemType, bucketId, getResources().getString(labelId))); + } + + /** + * Add a bucket, but only if it's interesting. + * Interesting means non-empty. + */ + private void addBucket(int itemType, String bucketId, + int labelId) { if (!isEmptyBucket(Item.convertItemTypeToIncludedMediaType(itemType), bucketId)) { mItems.add(new Item(itemType, bucketId, getResources().getString(labelId))); } @@ -489,49 +561,10 @@ public class GalleryPicker extends Activity { registerReceiver(mReceiver, intentFilter); MenuHelper.requestOrientation(this, mPrefs); - - // Warn the user if space is getting low - Thread t = new Thread(new Runnable() { - public void run() { - - // Check available space only if we are writable - if (ImageManager.hasStorage()) { - String storageDirectory = Environment.getExternalStorageDirectory().toString(); - StatFs stat = new StatFs(storageDirectory); - long remaining = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize(); - if (remaining < LOW_STORAGE_THRESHOLD) { - - mHandler.post(new Runnable() { - public void run() { - Toast.makeText(GalleryPicker.this.getApplicationContext(), - R.string.not_enough_space, 5000).show(); - } - }); - } - } - } - }); - t.start(); - - // 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; - } - } } + private void setBackgrounds(Resources r) { mFrameGalleryMask = r.getDrawable(R.drawable.frame_gallery_preview_album_mask); @@ -644,7 +677,6 @@ public class GalleryPicker extends Activity { super.onCreateOptionsMenu(menu); MenuHelper.addCaptureMenuItems(menu, this); - mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); menu.add(0, 0, 5, R.string.camerasettings) .setOnMenuItemClickListener(new OnMenuItemClickListener() { @@ -661,12 +693,6 @@ public class GalleryPicker extends Activity { return true; } - @Override - public boolean onPrepareOptionsMenu(android.view.Menu menu) { - MenuHelper.setFlipOrientationEnabled(this, mFlipItem); - return true; - } - private boolean isEmptyBucket(int mediaTypes, String bucketId) { // TODO: Find a more efficient way of calculating this ImageManager.IImageList list = createImageList(mediaTypes, bucketId); @@ -678,6 +704,16 @@ public class GalleryPicker extends Activity { } } + private int bucketItemCount(int mediaTypes, String bucketId) { + // TODO: Find a more efficient way of calculating this + ImageManager.IImageList list = createImageList(mediaTypes, bucketId); + try { + return list.getCount(); + } + finally { + list.deactivate(); + } + } private ImageManager.IImageList createImageList(int mediaTypes, String bucketId) { return ImageManager.instance().allImages( this, diff --git a/src/com/android/camera/ImageGallery2.java b/src/com/android/camera/ImageGallery2.java index 92e9c57..d58f04c 100644 --- a/src/com/android/camera/ImageGallery2.java +++ b/src/com/android/camera/ImageGallery2.java @@ -73,7 +73,6 @@ public class ImageGallery2 extends Activity { private Dialog mMediaScanningDialog; - private MenuItem mFlipItem; private MenuItem mSlideShowItem; private SharedPreferences mPrefs; @@ -187,7 +186,10 @@ public class ImageGallery2 extends Activity { private Runnable mDeletePhotoRunnable = new Runnable() { public void run() { mGvs.clearCache(); - mAllImages.removeImage(mSelectedImageGetter.getCurrentImage()); + IImage currentImage = mSelectedImageGetter.getCurrentImage(); + if (currentImage != null) { + mAllImages.removeImage(currentImage); + } mGvs.invalidate(); mGvs.requestLayout(); mGvs.start(); @@ -356,8 +358,10 @@ public class ImageGallery2 extends Activity { switch (requestCode) { case CROP_MSG: { if (Config.LOGV) Log.v(TAG, "onActivityResult " + data); - setResult(resultCode, data); - finish(); + if (resultCode == RESULT_OK) { + setResult(resultCode, data); + finish(); + } break; } case VIEW_MSG: { @@ -504,7 +508,6 @@ public class ImageGallery2 extends Activity { mThumbnailCheckThread = new CameraThread(new Runnable() { public void run() { android.content.res.Resources resources = getResources(); - boolean loadingVideos = mInclusion == ImageManager.INCLUDE_VIDEOS; final TextView progressTextView = (TextView) findViewById(R.id.loading_text); final String progressTextFormatString = resources.getString(R.string.loading_progress_format_string); @@ -547,7 +550,8 @@ public class ImageGallery2 extends Activity { return !mPausing; } }; - allImages(true).checkThumbnails(r); + ImageManager.IImageList imageList = allImages(true); + imageList.checkThumbnails(r, imageList.getCount()); mWakeLock.release(); mThumbnailCheckThread = null; mHandler.post(new Runnable() { @@ -572,12 +576,13 @@ public class ImageGallery2 extends Activity { @Override public boolean onCreateOptionsMenu(android.view.Menu menu) { MenuItem item; - MenuHelper.addCaptureMenuItems(menu, this); - if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { - mSlideShowItem = addSlideShowMenu(menu, 5); + if (! isPickIntent()) { + MenuHelper.addCaptureMenuItems(menu, this); + if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { + mSlideShowItem = addSlideShowMenu(menu, 5); + } } - mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); item = menu.add(0, 0, 1000, R.string.camerasettings); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @@ -599,9 +604,10 @@ public class ImageGallery2 extends Activity { if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { boolean videoSelected = isVideoSelected(); // TODO: Only enable slide show if there is at least one image in the folder. - mSlideShowItem.setEnabled(!videoSelected); + if (mSlideShowItem != null) { + mSlideShowItem.setEnabled(!videoSelected); + } } - MenuHelper.setFlipOrientationEnabled(this, mFlipItem); return true; } @@ -745,7 +751,7 @@ public class ImageGallery2 extends Activity { setVerticalScrollBarEnabled(true); initializeScrollbars(context.obtainStyledAttributes(android.R.styleable.View)); - mGestureDetector = new GestureDetector(new SimpleOnGestureListener() { + mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { if (mScroller != null && !mScroller.isFinished()) { @@ -1037,7 +1043,7 @@ public class ImageGallery2 extends Activity { loadNext(); synchronized (ImageBlockManager.this) { - if (workCounter == mWorkCounter) { + if ((workCounter == mWorkCounter) && (! mDone)) { try { ImageBlockManager.this.wait(); } catch (InterruptedException ex) { diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java index 4354e92..b21b243 100755 --- a/src/com/android/camera/ImageManager.java +++ b/src/com/android/camera/ImageManager.java @@ -63,7 +63,7 @@ import java.util.HashMap; */ public class ImageManager { public static final String CAMERA_IMAGE_BUCKET_NAME = - Environment.getExternalStorageDirectory().toString() + "/dcim/Camera"; + Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera"; public static final String CAMERA_IMAGE_BUCKET_ID = getBucketId(CAMERA_IMAGE_BUCKET_NAME); /** @@ -830,7 +830,7 @@ public class ImageManager { } String randomAccessFilePath(int version) { - String directoryName = Environment.getExternalStorageDirectory().toString() + "/dcim/.thumbnails"; + String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails"; String path = directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); return path; } @@ -1035,25 +1035,6 @@ public class ImageManager { return bitmap; } - private Bitmap createVideoThumbnail(String filePath) { - Bitmap bitmap = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); - retriever.setDataSource(filePath); - bitmap = retriever.captureFrame(); - } catch (RuntimeException ex) { - // Assume this is a corrupt video file. - } finally { - try { - retriever.release(); - } catch (RuntimeException ex) { - // Ignore failures while cleaning up. - } - } - return bitmap; - } - // returns id public long checkThumbnail(BaseImage existingImage, Cursor c, int i) { long magic, fileMagic = 0, id; @@ -1114,13 +1095,13 @@ public class ImageManager { } } if (filePath != null) { - bitmap = createThumbnailFromEXIF(filePath, id); - if (bitmap == null) { - String mimeType = c.getString(indexMimeType()); - boolean isVideo = isVideoMimeType(mimeType); - if (isVideo) { - bitmap = createVideoThumbnail(filePath); - } else { + String mimeType = c.getString(indexMimeType()); + boolean isVideo = isVideoMimeType(mimeType); + if (isVideo) { + bitmap = createVideoThumbnail(filePath); + } else { + bitmap = createThumbnailFromEXIF(filePath, id); + if (bitmap == null) { bitmap = createThumbnailFromUri(c, id); } } @@ -1166,13 +1147,13 @@ public class ImageManager { } } - public void checkThumbnails(ThumbCheckCallback cb) { + public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) { Cursor c = Images.Media.query( mContentResolver, mBaseUri, new String[] { "_id", "mini_thumb_magic" }, - "mini_thumb_magic isnull and " + sWhereClause, - sAcceptableImageTypes, + thumbnailWhereClause(), + thumbnailWhereClauseArgs(), "_id ASC"); int count = c.getCount(); @@ -1201,7 +1182,6 @@ public class ImageManager { c = getCursor(); try { if (VERBOSE) Log.v(TAG, "checkThumbnails found " + c.getCount()); - int max = c.getCount(); int current = 0; for (int i = 0; i < c.getCount(); i++) { try { @@ -1211,7 +1191,7 @@ public class ImageManager { break; } if (cb != null) { - if (!cb.checking(current, max)) { + if (!cb.checking(current, totalThumbnails)) { if (VERBOSE) Log.v(TAG, "got false from checking... break <<<<<<<<<<<<<<<<<<<<<<<<"); break; } @@ -1228,6 +1208,14 @@ public class ImageManager { } } + protected String thumbnailWhereClause() { + return sMiniThumbIsNull + " and " + sWhereClause; + } + + protected String[] thumbnailWhereClauseArgs() { + return sAcceptableImageTypes; + } + public void commitChanges() { synchronized (mCursor) { mCursor.commitUpdates(); @@ -1696,7 +1684,7 @@ public class ImageManager { public boolean checking(int current, int count); } - public abstract void checkThumbnails(ThumbCheckCallback cb); + public abstract void checkThumbnails(ThumbCheckCallback cb, int totalCount); public abstract void commitChanges(); @@ -2085,6 +2073,7 @@ public class ImageManager { final static private String sWhereClause = "(" + Images.Media.MIME_TYPE + "=? or " + Images.Media.MIME_TYPE + "=?" + ")"; final static private String[] sAcceptableImageTypes = new String[] { "image/jpeg", "image/png" }; + final static private String sMiniThumbIsNull = "mini_thumb_magic isnull"; private static final String[] IMAGE_PROJECTION = new String[] { "_id", @@ -2362,7 +2351,7 @@ public class ImageManager { } @Override - public void checkThumbnails(ThumbCheckCallback cb) { + public void checkThumbnails(ThumbCheckCallback cb, int totalCount) { // do nothing } @@ -2465,13 +2454,12 @@ public class ImageManager { } } - public void checkThumbnails(ThumbCheckCallback cb) { - // TODO this isn't quite right because we need to get the - // total from each sub item and provide that in the callback - final IImageList sublist[] = mSubList; - final int length = sublist.length; - for (int i = 0; i < length; i++) - sublist[i].checkThumbnails(cb); + public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) { + for (IImageList i : mSubList) { + int count = i.getCount(); + i.checkThumbnails(cb, totalThumbnails); + totalThumbnails -= count; + } } public void commitChanges() { @@ -3284,7 +3272,6 @@ public class ImageManager { HashMap<String, String> hash = new HashMap<String, String>(); if (c != null && c.moveToFirst()) { do { - Log.e(TAG, "id: " + c.getString(1) + " display_name: " + c.getString(0)); hash.put(c.getString(1), c.getString(0)); } while (c.moveToNext()); } @@ -3303,6 +3290,16 @@ public class ImageManager { return null; } + @Override + protected String thumbnailWhereClause() { + return sMiniThumbIsNull; + } + + @Override + protected String[] thumbnailWhereClauseArgs() { + return null; + } + protected Cursor createCursor() { Cursor c = Images.Media.query( @@ -3366,13 +3363,14 @@ public class ImageManager { return thumbnail; } - private final Bitmap sDefaultThumbnail = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565); private String sortOrder() { - return Video.Media.DATE_MODIFIED + (mSort == SORT_ASCENDING ? " ASC " : " DESC"); + return Video.Media.DATE_TAKEN + (mSort == SORT_ASCENDING ? " ASC " : " DESC"); } } + private final static Bitmap sDefaultThumbnail = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565); + /** * Represents a particular video and provides access * to the underlying data and two thumbnail bitmaps @@ -3537,10 +3535,10 @@ public class ImageManager { sb.append("" + mId); return sb.toString(); } - - private final Bitmap sNoImageBitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.RGB_565); } + private final static Bitmap sNoImageBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); + /* * How much quality to use when storing the thumbnail. */ @@ -3900,7 +3898,8 @@ public class ImageManager { public IImageList emptyImageList() { return new IImageList() { - public void checkThumbnails(com.android.camera.ImageManager.IImageList.ThumbCheckCallback cb) { + public void checkThumbnails(ImageManager.IImageList.ThumbCheckCallback cb, + int totalThumbnails) { } public void commitChanges() { @@ -3936,10 +3935,11 @@ public class ImageManager { public void removeImageAt(int i) { } - public void removeOnChangeListener(com.android.camera.ImageManager.IImageList.OnChange changeCallback) { + public void removeOnChangeListener(ImageManager.IImageList.OnChange changeCallback) { } - public void setOnChangeListener(com.android.camera.ImageManager.IImageList.OnChange changeCallback, Handler h) { + public void setOnChangeListener(ImageManager.IImageList.OnChange changeCallback, + Handler h) { } }; @@ -4027,7 +4027,7 @@ public class ImageManager { // Create a temporary file to see whether a volume is really writeable. It's important not to // put it in the root directory which may have a limit on the number of files. static private boolean checkFsWritable() { - String directoryName = Environment.getExternalStorageDirectory().toString() + "/dcim"; + String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM"; File directory = new File(directoryName); if (!directory.isDirectory()) { if (!directory.mkdirs()) { @@ -4100,4 +4100,30 @@ public class ImageManager { Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>>>>>> isMediaScannerScanning returning " + result); return result; } + + /** + * Create a video thumbnail for a video. May return null if the video is corrupt. + * @param filePath + * @return + */ + public static Bitmap createVideoThumbnail(String filePath) { + Bitmap bitmap = null; + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); + retriever.setDataSource(filePath); + bitmap = retriever.captureFrame(); + } catch(IllegalArgumentException ex) { + // Assume this is a corrupt video file + } catch (RuntimeException ex) { + // Assume this is a corrupt video file. + } finally { + try { + retriever.release(); + } catch (RuntimeException ex) { + // Ignore failures while cleaning up. + } + } + return bitmap; + } } diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java index 1976444..d4358f6 100644 --- a/src/com/android/camera/MenuHelper.java +++ b/src/com/android/camera/MenuHelper.java @@ -16,13 +16,15 @@ package com.android.camera; +import java.util.ArrayList; + import android.app.Activity; import android.app.AlertDialog; -import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Environment; @@ -37,16 +39,10 @@ import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.MenuItem.OnMenuItemClickListener; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; import android.widget.ImageView; -import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -import java.util.ArrayList; - import com.android.camera.ImageManager.IImage; public class MenuHelper { @@ -357,8 +353,14 @@ public class MenuHelper { String codec = retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_CODEC); - ((TextView)d.findViewById(R.id.details_codec_value)) - .setText(codec); + + if (codec == null) { + d.findViewById(R.id.details_codec_row). + setVisibility(View.GONE); + } else { + ((TextView)d.findViewById(R.id.details_codec_value)) + .setText(codec); + } } catch(RuntimeException ex) { // Assume this is a corrupt video file. } finally { @@ -531,9 +533,26 @@ public class MenuHelper { } static void gotoCameraImageGallery(Activity activity) { + gotoGallery(activity, R.string.gallery_camera_bucket_name, ImageManager.INCLUDE_IMAGES); + } + + static void gotoCameraVideoGallery(Activity activity) { + gotoGallery(activity, R.string.gallery_camera_videos_bucket_name, + ImageManager.INCLUDE_VIDEOS); + } + + static private void gotoGallery(Activity activity, int windowTitleId, int mediaTypes) { Uri target = Images.Media.INTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build(); Intent intent = new Intent(Intent.ACTION_VIEW, target); + intent.putExtra("windowTitle", activity.getString(windowTitleId)); + intent.putExtra("mediaTypes", mediaTypes); + // Request unspecified so that we match the current camera orientation rather than + // matching the "flip orientation" preference. + // Disabled because people don't care for it. Also it's + // not as compelling now that we have implemented have quick orientation flipping. + // intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, + // android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); try { activity.startActivity(intent); } catch (ActivityNotFoundException e) { @@ -575,20 +594,22 @@ public class MenuHelper { } static MenuItem addFlipOrientation(Menu menu, final Activity activity, final SharedPreferences prefs) { // position 41 after rotate + // D return menu .add(Menu.CATEGORY_SECONDARY, 304, 41, R.string.flip_orientation) .setOnMenuItemClickListener( new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - int current = activity.getRequestedOrientation(); + // Check what our actual orientation is + int current = activity.getResources().getConfiguration().orientation; int newOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - if (current == android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { + if (current == Configuration.ORIENTATION_LANDSCAPE) { newOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } SharedPreferences.Editor editor = prefs.edit(); editor.putInt("nuorientation", newOrientation); editor.commit(); - requestOrientation(activity, prefs); + requestOrientation(activity, prefs, true); return true; } }) @@ -596,15 +617,24 @@ public class MenuHelper { } static void requestOrientation(Activity activity, SharedPreferences prefs) { + requestOrientation(activity, prefs, false); + } + + static private void requestOrientation(Activity activity, SharedPreferences prefs, + boolean ignoreIntentExtra) { int req = prefs.getInt("nuorientation", android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); // A little trick: use USER instead of UNSPECIFIED, so we ignore the // orientation set by the activity below. It may have forced a landscape // orientation, which the user has now cleared here. - activity.setRequestedOrientation( - req == android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - ? android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER - : req); + if (req == android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { + req = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; + } + if (! ignoreIntentExtra) { + Intent intent = activity.getIntent(); + req = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, req); + } + activity.setRequestedOrientation(req); } static void setFlipOrientationEnabled(Activity activity, MenuItem flipItem) { @@ -628,13 +658,20 @@ public class MenuHelper { return durationValue; } - public static void showStorageToast(Activity activity) { + showStorageToast(activity, calculatePicturesRemaining()); + } + + public static void showStorageToast(Activity activity, int remaining) { String noStorageText = null; - int remaining = calculatePicturesRemaining(); if (remaining == MenuHelper.NO_STORAGE_ERROR) { - noStorageText = activity.getString(R.string.no_storage); + String state = Environment.getExternalStorageState(); + if (state == Environment.MEDIA_CHECKING) { + noStorageText = activity.getString(R.string.preparing_sd); + } else { + noStorageText = activity.getString(R.string.no_storage); + } } else if (remaining < 1) { noStorageText = activity.getString(R.string.not_enough_space); } diff --git a/src/com/android/camera/MovieView.java b/src/com/android/camera/MovieView.java index b93336c..091cc28 100644 --- a/src/com/android/camera/MovieView.java +++ b/src/com/android/camera/MovieView.java @@ -50,6 +50,11 @@ public class MovieView extends Activity implements MediaPlayer.OnErrorListener, private View mProgressView; private boolean mFinishOnCompletion; private Uri mUri; + + // State maintained for proper onPause/OnResume behaviour. + private int mPositionWhenPaused = -1; + private boolean mWasPlayingWhenPaused = false; + public MovieView() { } @@ -172,17 +177,42 @@ public class MovieView extends Activity implements MediaPlayer.OnErrorListener, if ("content".equalsIgnoreCase(scheme)) { ContentValues values = new ContentValues(); values.put(Video.VideoColumns.BOOKMARK, Integer.toString(bookmark)); - getContentResolver().update(mUri, values, null, null); - } + try { + getContentResolver().update(mUri, values, null, null); + } catch (SecurityException ex) { + // Ignore, can happen if we try to set the bookmark on a read-only resource + // such as a video attached to GMail. + } catch (SQLiteException e) { + // ignore. can happen if the content doesn't support a bookmark column. + } + } } @Override public void onPause() { mHandler.removeCallbacksAndMessages(null); setBookmark(mVideoView.getCurrentPosition()); + + mPositionWhenPaused = mVideoView.getCurrentPosition(); + mWasPlayingWhenPaused = mVideoView.isPlaying(); + mVideoView.stopPlayback(); + super.onPause(); } + @Override + public void onResume() { + if (mPositionWhenPaused >= 0) { + mVideoView.setVideoURI(mUri); + mVideoView.seekTo(mPositionWhenPaused); + if (mWasPlayingWhenPaused) { + mVideoView.start(); + } + } + + super.onResume(); + } + Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { diff --git a/src/com/android/camera/SlideShow.java b/src/com/android/camera/SlideShow.java index 2be99ac..23c7d4a 100644 --- a/src/com/android/camera/SlideShow.java +++ b/src/com/android/camera/SlideShow.java @@ -284,7 +284,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory throw new UnsupportedOperationException(); } - public void checkThumbnails(ThumbCheckCallback cb) { + public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) { // TODO Auto-generated method stub } diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index 4828b71..3474da6 100644 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -20,11 +20,12 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import android.app.Activity; import android.app.AlertDialog; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.ContentValues; @@ -58,12 +59,14 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.MenuItem.OnMenuItemClickListener; +import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; -public class VideoCamera extends Activity implements View.OnClickListener, SurfaceHolder.Callback { +public class VideoCamera extends Activity implements View.OnClickListener, + ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback { private static final String TAG = "videocamera"; @@ -119,8 +122,9 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa int mCurrentZoomIndex = 0; - private ImageView mModeIndicatorView; + private ShutterButton mShutterButton; private TextView mRecordingTimeView; + private boolean mHasSdCard; ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); @@ -136,7 +140,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa switch (msg.what) { case CLEAR_SCREEN_DELAY: { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + clearScreenOnFlag(); break; } @@ -185,9 +189,11 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa // TODO put up a "please wait" message // TODO also listen for the media scanner finished message showStorageToast(); + mHasSdCard = true; } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { // SD card unavailable showStorageToast(); + mHasSdCard = false; } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { @@ -215,9 +221,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); requestWindowFeature(Window.FEATURE_PROGRESS); - - Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.video_camera); mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview); @@ -236,12 +239,13 @@ 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.cancel, R.id.attach, R.id.mode_indicator}; + R.id.cancel, R.id.attach}; for (int id : ids) { findViewById(id).setOnClickListener(this); } - mModeIndicatorView = (ImageView) findViewById(R.id.mode_indicator); + mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); + mShutterButton.setOnShutterButtonListener(this); mRecordingTimeView = (TextView) findViewById(R.id.recording_time); mVideoFrame = (ImageView) findViewById(R.id.video_frame); } @@ -253,35 +257,13 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } super.onStart(); - final View hintView = findViewById(R.id.hint_toast); - if (hintView != null) - hintView.setVisibility(View.GONE); - Thread t = new Thread(new Runnable() { public void run() { final boolean storageOK = getAvailableStorage() >= LOW_STORAGE_THRESHOLD; - if (hintView == null) - return; - if (storageOK) { + if (!storageOK) { mHandler.post(new Runnable() { public void run() { - hintView.setVisibility(View.VISIBLE); - } - }); - mHandler.postDelayed(new Runnable() { - public void run() { - Animation a = new android.view.animation.AlphaAnimation(1F, 0F); - a.setDuration(500); - a.startNow(); - hintView.setAnimation(a); - hintView.setVisibility(View.GONE); - } - }, 3000); - } else { - mHandler.post(new Runnable() { - public void run() { - hintView.setVisibility(View.GONE); showStorageToast(); } }); @@ -325,19 +307,29 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa doPlayCurrentVideo(); break; } + } + } - case R.id.mode_indicator: - if (mMediaRecorderRecording) { - stopVideoRecordingAndDisplayDialog(); - } else if (mVideoFrame.getVisibility() == View.VISIBLE) { - doStartCaptureMode(); - } else { - startVideoRecording(); + public void onShutterButtonFocus(ShutterButton button, boolean pressed) { + switch (button.getId()) { + case R.id.shutter_button: + if (pressed) { + if (mMediaRecorderRecording) { + stopVideoRecordingAndDisplayDialog(); + } else if (mVideoFrame.getVisibility() == View.VISIBLE) { + doStartCaptureMode(); + } else { + startVideoRecording(); + } } break; } } + public void onShutterButtonClick(ShutterButton button) { + // Do nothing (everything happens in onShutterButtonFocus). + } + private void doStartCaptureMode() { if (isVideoCaptureIntent()) { discardCurrentVideoAndStartPreview(); @@ -379,7 +371,8 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa Log.v(TAG, "onResume " + this.hashCode()); } super.onResume(); - mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); + + setScreenTimeoutLong(); mPausing = false; @@ -391,6 +384,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa intentFilter.addDataScheme("file"); registerReceiver(mReceiver, intentFilter); mDidRegister = true; + mHasSdCard = ImageManager.hasStorage(); mBlackout.setVisibility(View.INVISIBLE); if (mVideoFrameBitmap == null) { @@ -406,7 +400,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa Log.v(TAG, "onStop " + this.hashCode()); } stopVideoRecording(); - mHandler.removeMessages(CLEAR_SCREEN_DELAY); + setScreenTimeoutSystemDefault(); super.onStop(); } @@ -427,12 +421,12 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa mDidRegister = false; } mBlackout.setVisibility(View.VISIBLE); + setScreenTimeoutSystemDefault(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + setScreenTimeoutLong(); switch (keyCode) { case KeyEvent.KEYCODE_BACK: @@ -445,19 +439,31 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa return true; } break; - case KeyEvent.KEYCODE_FOCUS: - return true; case KeyEvent.KEYCODE_CAMERA: - case KeyEvent.KEYCODE_DPAD_CENTER: if (event.getRepeatCount() == 0) { - if (!mMediaRecorderRecording) { - startVideoRecording(); + // If we get a dpad center event without any focused view, move the + // focus to the shutter button and press it. + if (mShutterButton.isInTouchMode()) { + mShutterButton.requestFocusFromTouch(); } else { - stopVideoRecordingAndDisplayDialog(); + mShutterButton.requestFocus(); } + mShutterButton.setPressed(true); return true; } return true; + case KeyEvent.KEYCODE_DPAD_CENTER: + if (event.getRepeatCount() == 0) { + // If we get a dpad center event without any focused view, move the + // focus to the shutter button and press it. + if (mShutterButton.isInTouchMode()) { + mShutterButton.requestFocusFromTouch(); + } else { + mShutterButton.requestFocus(); + } + mShutterButton.setPressed(true); + } + break; case KeyEvent.KEYCODE_MENU: if (mMediaRecorderRecording) { stopVideoRecordingAndDisplayDialog(); @@ -469,6 +475,16 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa return super.onKeyDown(keyCode, event); } + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) { + case KeyEvent.KEYCODE_CAMERA: + mShutterButton.setPressed(false); + return true; + } + return super.onKeyUp(keyCode, event); + } + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { stopVideoRecording(); initializeVideo(); @@ -483,13 +499,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } void gotoGallery() { - Uri target = Video.Media.INTERNAL_CONTENT_URI; - Intent intent = new Intent(Intent.ACTION_VIEW, target); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Could not start gallery activity", e); - } + MenuHelper.gotoCameraVideoGallery(this); } @Override @@ -509,36 +519,41 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - addBaseMenuItems(menu); - MenuHelper.addImageMenuItems( - menu, - MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU, - false, - VideoCamera.this, - mHandler, - - // Handler for deletion - new Runnable() { - public void run() { - // What do we do here? - // mContentResolver.delete(uri, null, null); - } - }, - new MenuHelper.MenuInvoker() { - public void run(final MenuHelper.MenuCallback cb) { - } - }); - MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, - R.string.camera_gallery_photos_text).setOnMenuItemClickListener( - new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - gotoGallery(); - return true; - } - }); - gallery.setIcon(android.R.drawable.ic_menu_gallery); + if (isVideoCaptureIntent()) { + // No options menu for attach mode. + return false; + } else { + addBaseMenuItems(menu); + MenuHelper.addImageMenuItems( + menu, + MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU, + false, + VideoCamera.this, + mHandler, + + // Handler for deletion + new Runnable() { + public void run() { + // What do we do here? + // mContentResolver.delete(uri, null, null); + } + }, + new MenuHelper.MenuInvoker() { + public void run(final MenuHelper.MenuCallback cb) { + } + }); + MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, + R.string.camera_gallery_photos_text).setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + gotoGallery(); + return true; + } + }); + gallery.setIcon(android.R.drawable.ic_menu_gallery); + } return true; } @@ -731,7 +746,11 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; File cameraDir = new File(cameraDirPath); cameraDir.mkdirs(); - String filename = cameraDirPath + "/" + Long.toString(dateTaken) + ".3gp"; + SimpleDateFormat dateFormat = new SimpleDateFormat( + getString(R.string.video_file_name_format)); + Date date = new Date(dateTaken); + String filepart = dateFormat.format(date); + String filename = cameraDirPath + "/" + filepart + ".3gp"; ContentValues values = new ContentValues(7); values.put(Video.Media.TITLE, title); values.put(Video.Media.DISPLAY_NAME, displayName); @@ -809,11 +828,17 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa Log.v(TAG, "startVideoRecording"); if (!mMediaRecorderRecording) { + if (!mHasSdCard) { + Toast.makeText(this, getString( + R.string.no_storage), Toast.LENGTH_LONG).show(); + Log.v(TAG, "No SD card, ignore start recording"); + return; + } + // Check mMediaRecorder to see whether it is initialized or not. if (mMediaRecorder == null) { initializeVideo(); } - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); try { mMediaRecorder.start(); // Recording is now started } catch (RuntimeException e) { @@ -826,6 +851,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa mRecordingTimeView.setText(""); mRecordingTimeView.setVisibility(View.VISIBLE); mHandler.sendEmptyMessage(UPDATE_RECORD_TIME); + setScreenTimeoutInfinite(); } } @@ -833,7 +859,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa int drawableId = showRecording ? R.drawable.ic_camera_bar_indicator_record : R.drawable.ic_camera_indicator_video; Drawable drawable = getResources().getDrawable(drawableId); - mModeIndicatorView.setImageDrawable(drawable); + mShutterButton.setImageDrawable(drawable); } private void stopVideoRecordingAndDisplayDialog() { @@ -846,16 +872,33 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } private void showPostRecordingAlert() { - boolean isPick = isVideoCaptureIntent(); - int pickVisible = isPick ? View.VISIBLE : View.GONE; - int normalVisible = ! isPick ? View.VISIBLE : View.GONE; - mPostPictureAlert.findViewById(R.id.share).setVisibility(normalVisible); - mPostPictureAlert.findViewById(R.id.discard).setVisibility(normalVisible); - mPostPictureAlert.findViewById(R.id.attach).setVisibility(pickVisible); - mPostPictureAlert.findViewById(R.id.cancel).setVisibility(pickVisible); + int[] pickIds = {R.id.attach, R.id.cancel}; + int[] normalIds = {R.id.share, R.id.discard}; + int[] alwaysOnIds = {R.id.play}; + int[] hideIds = pickIds; + int[] connectIds = normalIds; + if (isVideoCaptureIntent()) { + hideIds = normalIds; + connectIds = pickIds; + } + for(int id : hideIds) { + mPostPictureAlert.findViewById(id).setVisibility(View.GONE); + } + connectAndFadeIn(connectIds); + connectAndFadeIn(alwaysOnIds); mPostPictureAlert.setVisibility(View.VISIBLE); } + private void connectAndFadeIn(int[] connectIds) { + for(int id : connectIds) { + View view = mPostPictureAlert.findViewById(id); + view.setOnClickListener(this); + Animation animation = new AlphaAnimation(0F, 1F); + animation.setDuration(500); + view.setAnimation(animation); + } + } + private void hidePostPictureAlert() { mPostPictureAlert.setVisibility(View.INVISIBLE); } @@ -878,14 +921,44 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa releaseMediaRecorder(); updateRecordingIndicator(false); mRecordingTimeView.setVisibility(View.GONE); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + setScreenTimeoutLong(); } if (mNeedToRegisterRecording) { registerVideo(); mNeedToRegisterRecording = false; } - if (mCameraVideoFilename != null){ - deleteVideoFile(mCameraVideoFilename); + mCameraVideoFilename = null; + } + + private void setScreenTimeoutSystemDefault() { + mHandler.removeMessages(CLEAR_SCREEN_DELAY); + clearScreenOnFlag(); + } + + private void setScreenTimeoutLong() { + mHandler.removeMessages(CLEAR_SCREEN_DELAY); + setScreenOnFlag(); + mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); + } + + private void setScreenTimeoutInfinite() { + mHandler.removeMessages(CLEAR_SCREEN_DELAY); + setScreenOnFlag(); + } + + private void clearScreenOnFlag() { + Window w = getWindow(); + final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + if ((w.getAttributes().flags & keepScreenOnFlag) != 0) { + w.clearFlags(keepScreenOnFlag); + } + } + + private void setScreenOnFlag() { + Window w = getWindow(); + final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + if ((w.getAttributes().flags & keepScreenOnFlag) == 0) { + w.addFlags(keepScreenOnFlag); } } @@ -897,7 +970,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa private void acquireAndShowVideoFrame() { recycleVideoFrameBitmap(); - mVideoFrameBitmap = createVideoThumbnail(mCurrentVideoFilename); + mVideoFrameBitmap = ImageManager.createVideoThumbnail(mCurrentVideoFilename); mVideoFrame.setImageBitmap(mVideoFrameBitmap); mVideoFrame.setVisibility(View.VISIBLE); } @@ -914,19 +987,5 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa mVideoFrameBitmap = null; } } - - private Bitmap createVideoThumbnail(String filePath) { - Bitmap bitmap = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); - retriever.setDataSource(filePath); - bitmap = retriever.captureFrame(); - } finally { - retriever.release(); - } - return bitmap; - } - } diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java index 9760562..293f26b 100644 --- a/src/com/android/camera/ViewImage.java +++ b/src/com/android/camera/ViewImage.java @@ -16,8 +16,9 @@ package com.android.camera; +import java.util.Random; + import android.app.Activity; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -28,12 +29,11 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.PowerManager; +import android.preference.PreferenceManager; import android.provider.MediaStore; import android.util.AttributeSet; import android.util.Config; import android.util.Log; -import android.view.GestureDetector; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -43,21 +43,17 @@ import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; -import android.view.animation.Animation; import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Scroller; -import android.widget.TextView; import android.widget.Toast; import android.widget.ZoomControls; -import android.preference.PreferenceManager; import com.android.camera.ImageManager.IImage; -import java.util.Random; - public class ViewImage extends Activity implements View.OnClickListener { static final String TAG = "ViewImage"; @@ -86,6 +82,7 @@ public class ViewImage extends Activity implements View.OnClickListener private boolean mFullScreenInNormalMode; private boolean mShowActionIcons; private View mActionIconPanel; + private View mShutterButton; private boolean mSortAscending = false; private int mSlideShowInterval; @@ -100,7 +97,6 @@ public class ViewImage extends Activity implements View.OnClickListener private Animation [] mSlideShowOutAnimation; private SharedPreferences mPrefs; - private MenuItem mFlipItem; private View mNextImageView, mPrevImageView; private Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F); @@ -130,8 +126,9 @@ public class ViewImage extends Activity implements View.OnClickListener private MenuHelper.MenuItemsResult mImageMenuRunnable; - Runnable mDismissOnScreenControlsRunnable; - ZoomControls mZoomControls; + private Runnable mDismissOnScreenControlsRunnable; + private ZoomControls mZoomControls; + private boolean mCameraReviewMode; public ViewImage() { } @@ -176,7 +173,9 @@ public class ViewImage extends Activity implements View.OnClickListener if (mZoomControls != null) { if (mZoomControls.getVisibility() == View.GONE) { mZoomControls.show(); - mZoomControls.requestFocus(); // this shouldn't be necessary + if (! mShowActionIcons) { + mZoomControls.requestFocus(); // this shouldn't be necessary + } } updateNextPrevControls(); scheduleDismissOnScreenControls(); @@ -210,21 +209,22 @@ public class ViewImage extends Activity implements View.OnClickListener mDismissOnScreenControlsRunnable = new Runnable() { public void run() { mZoomControls.hide(); + if (!mShowActionIcons) { + if (mNextImageView.getVisibility() == View.VISIBLE) { + Animation a = mHideNextImageViewAnimation; + a.setDuration(500); + a.startNow(); + mNextImageView.setAnimation(a); + mNextImageView.setVisibility(View.INVISIBLE); + } - if (mNextImageView.getVisibility() == View.VISIBLE) { - Animation a = mHideNextImageViewAnimation; - a.setDuration(500); - a.startNow(); - mNextImageView.setAnimation(a); - mNextImageView.setVisibility(View.INVISIBLE); - } - - if (mPrevImageView.getVisibility() == View.VISIBLE) { - Animation a = mHidePrevImageViewAnimation; - a.setDuration(500); - a.startNow(); - mPrevImageView.setAnimation(a); - mPrevImageView.setVisibility(View.INVISIBLE); + if (mPrevImageView.getVisibility() == View.VISIBLE) { + Animation a = mHidePrevImageViewAnimation; + a.setDuration(500); + a.startNow(); + mPrevImageView.setAnimation(a); + mPrevImageView.setVisibility(View.INVISIBLE); + } } } }; @@ -251,12 +251,12 @@ public class ViewImage extends Activity implements View.OnClickListener return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)); } - private static final boolean sDragLeftRight = false; private static final boolean sUseBounce = false; private static final boolean sAnimateTransitions = false; static public class ImageViewTouch extends ImageViewTouchBase { private ViewImage mViewImage; + private boolean mEnableTrackballScroll; private static int TOUCH_STATE_REST = 0; private static int TOUCH_STATE_LEFT_PRESS = 1; @@ -277,6 +277,10 @@ public class ViewImage extends Activity implements View.OnClickListener mViewImage = (ViewImage) context; } + public void setEnableTrackballScroll(boolean enable) { + mEnableTrackballScroll = enable; + } + protected void postTranslate(float dx, float dy, boolean bounceOK) { super.postTranslate(dx, dy); if (dx != 0F || dy != 0F) @@ -390,6 +394,14 @@ public class ViewImage extends Activity implements View.OnClickListener @Override public boolean onKeyDown(int keyCode, KeyEvent event) { + // Don't respond to arrow keys if trackball scrolling is not enabled + if (!mEnableTrackballScroll) { + if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP) + && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) { + return super.onKeyDown(keyCode, event); + } + } + int current = mViewImage.mCurrentPosition; int nextImagePos = -2; // default no next image @@ -557,7 +569,7 @@ public class ViewImage extends Activity implements View.OnClickListener { super.onCreateOptionsMenu(menu); - if (true) { + if (! mCameraReviewMode) { MenuItem item = menu.add(Menu.CATEGORY_SECONDARY, 203, 0, R.string.slide_show); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -570,8 +582,6 @@ public class ViewImage extends Activity implements View.OnClickListener item.setIcon(android.R.drawable.ic_menu_slideshow); } - mFlipItem = MenuHelper.addFlipOrientation(menu, ViewImage.this, mPrefs); - final SelectedImageGetter selectedImageGetter = new SelectedImageGetter() { public ImageManager.IImage getCurrentImage() { return mAllImages.getImageAt(mCurrentPosition); @@ -657,8 +667,6 @@ public class ViewImage extends Activity implements View.OnClickListener mImageMenuRunnable.gettingReadyToOpen(menu, mAllImages.getImageAt(mCurrentPosition)); } - MenuHelper.setFlipOrientationEnabled(this, mFlipItem); - menu.findItem(MenuHelper.MENU_IMAGE_SHARE).setEnabled(isCurrentImageShareable()); return true; @@ -986,8 +994,10 @@ public class ViewImage extends Activity implements View.OnClickListener ImageGetterCallback cb = new ImageGetterCallback() { public void completed(boolean wasCanceled) { - mImageViews[1].setFocusableInTouchMode(true); - mImageViews[1].requestFocus(); + if (!mShowActionIcons) { + mImageViews[1].setFocusableInTouchMode(true); + mImageViews[1].requestFocus(); + } } public boolean wantsThumbnail(int pos, int offset) { @@ -1037,6 +1047,11 @@ public class ViewImage extends Activity implements View.OnClickListener public void onCreate(Bundle instanceState) { super.onCreate(instanceState); + Intent intent = getIntent(); + mCameraReviewMode = intent.getBooleanExtra("com.android.camera.ReviewMode", false); + mFullScreenInNormalMode = intent.getBooleanExtra(MediaStore.EXTRA_FULL_SCREEN, true); + mShowActionIcons = intent.getBooleanExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, false); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); @@ -1047,6 +1062,10 @@ public class ViewImage extends Activity implements View.OnClickListener mImageViews[1] = (ImageViewTouch) findViewById(R.id.image2); mImageViews[2] = (ImageViewTouch) findViewById(R.id.image3); + for(ImageViewTouch v : mImageViews) { + v.setEnableTrackballScroll(!mShowActionIcons); + } + mScroller = (ScrollHandler)findViewById(R.id.scroller); makeGetter(); @@ -1066,16 +1085,16 @@ public class ViewImage extends Activity implements View.OnClickListener mSlideShowImageViews[0] = (ImageViewTouch) findViewById(R.id.image1_slideShow); mSlideShowImageViews[1] = (ImageViewTouch) findViewById(R.id.image2_slideShow); - for (int i = 0; i < mSlideShowImageViews.length; i++) { - mSlideShowImageViews[i].setImageBitmapResetBase(null, true, true); - mSlideShowImageViews[i].setVisibility(View.INVISIBLE); + for (ImageViewTouch v : mSlideShowImageViews) { + v.setImageBitmapResetBase(null, true, true); + v.setVisibility(View.INVISIBLE); + v.setEnableTrackballScroll(!mShowActionIcons); } mActionIconPanel = findViewById(R.id.action_icon_panel); { int[] pickIds = {R.id.attach, R.id.cancel}; int[] normalIds = {R.id.gallery, R.id.setas, R.id.share, R.id.discard}; - int[] alwaysOnIds = {R.id.mode_indicator }; int[] hideIds = pickIds; int[] connectIds = normalIds; if (isPickIntent()) { @@ -1083,15 +1102,18 @@ public class ViewImage extends Activity implements View.OnClickListener connectIds = pickIds; } for(int id : hideIds) { - findViewById(id).setVisibility(View.GONE); + mActionIconPanel.findViewById(id).setVisibility(View.GONE); } for(int id : connectIds) { - findViewById(id).setOnClickListener(this); - } - for(int id : alwaysOnIds) { - findViewById(id).setOnClickListener(this); + View view = mActionIconPanel.findViewById(id); + view.setOnClickListener(this); + Animation animation = new AlphaAnimation(0F, 1F); + animation.setDuration(500); + view.setAnimation(animation); } } + mShutterButton = findViewById(R.id.shutter_button); + mShutterButton.setOnClickListener(this); Uri uri = getIntent().getData(); @@ -1107,10 +1129,6 @@ public class ViewImage extends Activity implements View.OnClickListener return; } init(uri); - mFullScreenInNormalMode = getIntent().getBooleanExtra( - MediaStore.EXTRA_SHOW_ACTION_ICONS, false); - mShowActionIcons = getIntent().getBooleanExtra( - MediaStore.EXTRA_SHOW_ACTION_ICONS, false); Bundle b = getIntent().getExtras(); @@ -1124,6 +1142,7 @@ public class ViewImage extends Activity implements View.OnClickListener } if (mShowActionIcons) { mActionIconPanel.setVisibility(View.VISIBLE); + mShutterButton.setVisibility(View.VISIBLE); } } @@ -1140,6 +1159,12 @@ public class ViewImage extends Activity implements View.OnClickListener mNextImageView = findViewById(R.id.next_image); mPrevImageView = findViewById(R.id.prev_image); + if (mShowActionIcons) { + mNextImageView.setOnClickListener(this); + mNextImageView.setFocusable(true); + mPrevImageView.setOnClickListener(this); + mPrevImageView.setFocusable(true); + } setOrientation(); } @@ -1186,6 +1211,7 @@ public class ViewImage extends Activity implements View.OnClickListener ivt.clear(); } mActionIconPanel.setVisibility(View.GONE); + mShutterButton.setVisibility(View.GONE); if (false) { Log.v(TAG, "current is " + this.mSlideShowImageCurrent); @@ -1237,6 +1263,7 @@ public class ViewImage extends Activity implements View.OnClickListener } if (mShowActionIcons) { mActionIconPanel.setVisibility(View.VISIBLE); + mShutterButton.setVisibility(View.VISIBLE); } ImageViewTouchBase dst = mImageViews[1]; @@ -1381,12 +1408,21 @@ public class ViewImage extends Activity implements View.OnClickListener mGetter = new ImageGetter(); } - private void init(Uri uri) { + private boolean desiredSortOrder() { String sortOrder = mPrefs.getString("pref_gallery_sort_key", null); - mSortAscending = false; + boolean sortAscending = false; if (sortOrder != null) { - mSortAscending = sortOrder.equals("ascending"); + sortAscending = sortOrder.equals("ascending"); } + if (mCameraReviewMode) { + // Force left-arrow older pictures, right-arrow newer pictures. + sortAscending = true; + } + return sortAscending; + } + + private void init(Uri uri) { + mSortAscending = desiredSortOrder(); int sort = mSortAscending ? ImageManager.SORT_ASCENDING : ImageManager.SORT_DESCENDING; mAllImages = ImageManager.makeImageList(uri, this, sort); @@ -1436,12 +1472,7 @@ public class ViewImage extends Activity implements View.OnClickListener ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition); - String sortOrder = mPrefs.getString("pref_gallery_sort_key", null); - boolean sortAscending = false; - if (sortOrder != null) { - sortAscending = sortOrder.equals("ascending"); - } - if (sortAscending != mSortAscending) { + if (desiredSortOrder() != mSortAscending) { init(image.fullSizeImageUri()); } @@ -1496,8 +1527,12 @@ public class ViewImage extends Activity implements View.OnClickListener public void onClick(View v) { switch (v.getId()) { - case R.id.mode_indicator: { - MenuHelper.gotoStillImageCapture(this); + case R.id.shutter_button: { + if (mCameraReviewMode) { + finish(); + } else { + MenuHelper.gotoStillImageCapture(this); + } } break; @@ -1507,7 +1542,11 @@ public class ViewImage extends Activity implements View.OnClickListener break; case R.id.discard: { - MenuHelper.displayDeleteDialog(this, mDeletePhotoRunnable, true); + if (mCameraReviewMode) { + mDeletePhotoRunnable.run(); + } else { + MenuHelper.deletePhoto(this, mDeletePhotoRunnable); + } } break; @@ -1535,6 +1574,23 @@ public class ViewImage extends Activity implements View.OnClickListener } } break; + + case R.id.next_image: { + moveNextOrPrevious(1); + } + break; + + case R.id.prev_image: { + moveNextOrPrevious(-1); + } + break; + } + } + + private void moveNextOrPrevious(int delta) { + int nextImagePos = mCurrentPosition + delta; + if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) { + setImage(nextImagePos); } } } |