diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-01-09 17:51:25 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-01-09 17:51:25 -0800 |
commit | b97ccf3f20bee44daf70f10966809e39e30ab4f7 (patch) | |
tree | fb6c421cd04ffed74e50cd62e4fdab34d5a9f448 /src | |
parent | 0eaec58e292ce4eaa6baadecf643f0f65e48d278 (diff) | |
download | LegacyCamera-b97ccf3f20bee44daf70f10966809e39e30ab4f7.zip LegacyCamera-b97ccf3f20bee44daf70f10966809e39e30ab4f7.tar.gz LegacyCamera-b97ccf3f20bee44daf70f10966809e39e30ab4f7.tar.bz2 |
auto import from //branches/cupcake/...@125939
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/camera/Camera.java | 215 | ||||
-rw-r--r-- | src/com/android/camera/CameraSettings.java | 59 | ||||
-rw-r--r-- | src/com/android/camera/GalleryPicker.java | 7 | ||||
-rw-r--r-- | src/com/android/camera/GalleryPickerItem.java | 1 | ||||
-rw-r--r-- | src/com/android/camera/GallerySettings.java | 13 | ||||
-rw-r--r-- | src/com/android/camera/ImageGallery2.java | 70 | ||||
-rwxr-xr-x | src/com/android/camera/ImageManager.java | 80 | ||||
-rw-r--r-- | src/com/android/camera/MenuHelper.java | 192 | ||||
-rw-r--r-- | src/com/android/camera/VideoCamera.java | 252 | ||||
-rw-r--r-- | src/com/android/camera/ViewImage.java | 102 | ||||
-rw-r--r-- | src/com/android/camera/Wallpaper.java | 24 |
11 files changed, 697 insertions, 318 deletions
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index b79e7d1..993e15c 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -25,19 +25,22 @@ import java.util.ArrayList; import android.app.Activity; import android.app.ProgressDialog; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.hardware.Camera.PictureCallback; import android.hardware.Camera.Size; import android.location.Location; @@ -65,17 +68,18 @@ import android.view.Menu; import android.view.MenuItem; import android.view.OrientationListener; import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.MenuItem.OnMenuItemClickListener; +import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageView; import android.widget.Toast; +import com.android.camera.ImageManager.IImageList; + public class Camera extends Activity implements View.OnClickListener, SurfaceHolder.Callback { private static final String TAG = "camera"; @@ -88,7 +92,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol private static final int CLEAR_SCREEN_DELAY = 4; private static final int SCREEN_DELAY = 2 * 60 * 1000; - private static final int POST_PICTURE_ALERT_TIMEOUT = 6 * 1000; private static final int FOCUS_BEEP_VOLUME = 100; private static final int NO_STORAGE_ERROR = -1; @@ -105,8 +108,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol public static final int MENU_GALLERY_VIDEOS = 8; public static final int MENU_SAVE_SELECT_PHOTOS = 30; public static final int MENU_SAVE_NEW_PHOTO = 31; - public static final int MENU_SAVE_SELECTVIDEO = 32; - public static final int MENU_SAVE_TAKE_NEW_VIDEO = 33; public static final int MENU_SAVE_GALLERY_PHOTO = 34; public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35; public static final int MENU_SAVE_CAMERA_DONE = 36; @@ -154,7 +155,8 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol boolean mMenuSelectionMade; - View mPostPictureAlert; + ImageView mLastPictureButton; + Uri mLastPictureUri; LocationManager mLocationManager = null; private Animation mFocusBlinkAnimation; @@ -216,7 +218,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol // TODO remove polling mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 100); } else if (mStatus == SNAPSHOT_COMPLETED){ - hidePostPictureAlert(); mCaptureObject.dismissFreezeFrame(true); } break; @@ -316,9 +317,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol Log.v(TAG, "got RawPictureCallback..."); mRawPictureCallbackTime = System.currentTimeMillis(); mBlackout.setVisibility(View.INVISIBLE); - if (!isPickIntent() && mPreferences.getBoolean("pref_camera_postpicturemenu_key", true)) { - showPostPictureAlert(); - } } }; @@ -337,23 +335,10 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mStatus = SNAPSHOT_COMPLETED; - if (!mPreferences.getBoolean("pref_camera_postpicturemenu_key", true)) { - if (mKeepAndRestartPreview) { - long delay = 1500 - (System.currentTimeMillis() - mRawPictureCallbackTime); - mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, Math.max(delay, 0)); - } - return; - } - if (mKeepAndRestartPreview) { - mKeepAndRestartPreview = false; - mPostPictureAlert.setVisibility(View.INVISIBLE); - - // Post this message so that we can finish processing the request. This also - // prevents the preview from showing up before mPostPictureAlert is dismissed. - mHandler.sendEmptyMessage(RESTART_PREVIEW); + long delay = 1500 - (System.currentTimeMillis() - mRawPictureCallbackTime); + mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, Math.max(delay, 0)); } - } }; @@ -489,6 +474,8 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol if (!captureOnly) { storeImage(data, loc); sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", mLastContentUri)); + setLastPictureThumb(data); + dismissFreezeFrame(true); } else { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 4; @@ -517,6 +504,46 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } + 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. @@ -619,7 +646,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) { mKeepAndRestartPreview = true; mHandler.sendEmptyMessage(RESTART_PREVIEW); - mPostPictureAlert.setVisibility(View.INVISIBLE); return; } @@ -633,7 +659,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mStatus = SNAPSHOT_IN_PROGRESS; - mKeepAndRestartPreview = !mPreferences.getBoolean("pref_camera_postpicturemenu_key", true); + mKeepAndRestartPreview = true; boolean getContentAction = isPickIntent(); if (getContentAction) { @@ -712,17 +738,9 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mBlackout = (ImageView) findViewById(R.id.blackout); mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); - mPostPictureAlert = findViewById(R.id.post_picture_panel); - View b; - - b = findViewById(R.id.discard); - b.setOnClickListener(this); - - b = findViewById(R.id.share); - b.setOnClickListener(this); - - b = findViewById(R.id.setas); - b.setOnClickListener(this); + mLastPictureButton = (ImageView) findViewById(R.id.last_picture_button); + mLastPictureButton.setOnClickListener(this); + mLastPictureButton.setVisibility(View.INVISIBLE); try { mClickSound = new MediaPlayer(); @@ -796,59 +814,9 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol public void onClick(View v) { switch (v.getId()) { - /* - case R.id.save: { - mPostPictureAlert.setVisibility(View.GONE); - postAfterKeep(null); - break; - } - */ - case R.id.discard: { - if (mCaptureObject != null) { - mCaptureObject.cancelSave(); - Uri uri = mCaptureObject.getLastCaptureUri(); - if (uri != null) { - mContentResolver.delete(uri, null, null); - } - mCaptureObject.dismissFreezeFrame(true); - } - mPostPictureAlert.setVisibility(View.GONE); - break; - } - - case R.id.share: { - mPostPictureAlert.setVisibility(View.GONE); - postAfterKeep(new Runnable() { - public void run() { - Uri u = mCaptureObject.getLastCaptureUri(); - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("image/jpeg"); - intent.putExtra(Intent.EXTRA_STREAM, u); - try { - startActivity(Intent.createChooser(intent, getText(R.string.sendImage))); - } catch (android.content.ActivityNotFoundException ex) { - Toast.makeText(Camera.this, R.string.no_way_to_share_image, Toast.LENGTH_SHORT).show(); - } - } - }); - break; - } - - case R.id.setas: { - mPostPictureAlert.setVisibility(View.GONE); - postAfterKeep(new Runnable() { - public void run() { - Uri u = mCaptureObject.getLastCaptureUri(); - Intent intent = new Intent(Intent.ACTION_ATTACH_DATA, u); - try { - startActivity(Intent.createChooser(intent, getText(R.string.setImage))); - } catch (android.content.ActivityNotFoundException ex) { - Toast.makeText(Camera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); - } - } - }); + case R.id.last_picture_button: { + viewLastImage(); break; } } @@ -903,6 +871,16 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } mBlackout.setVisibility(View.INVISIBLE); + + if (mLastPictureUri != null) { + IImageList list = ImageManager.makeImageList(mLastPictureUri, this, + ImageManager.SORT_ASCENDING); + if (list.getImageForUri(mLastPictureUri) == null) { + mLastPictureUri = null; + mLastPictureButton.setVisibility(View.INVISIBLE); + } + list.deactivate(); + } } private ImageManager.DataLocation dataLocation() { @@ -921,7 +899,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol @Override protected void onPause() { keep(); - mPostPictureAlert.setVisibility(View.INVISIBLE); mPausing = true; mOrientationListener.disable(); @@ -1012,11 +989,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) { - if (mPostPictureAlert.getVisibility() == View.VISIBLE) { - keep(); - mPostPictureAlert.setVisibility(View.INVISIBLE); - restartPreview(); - } // ignore backs while we're taking a picture return true; } @@ -1058,9 +1030,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } return true; - case KeyEvent.KEYCODE_MENU: - mPostPictureAlert.setVisibility(View.INVISIBLE); - break; } return super.onKeyDown(keyCode, event); @@ -1260,12 +1229,30 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } void gotoGallery() { - Uri target = Images.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.gotoCameraImageGallery(this); + } + + private void viewLastImage() { + Uri targetUri = mLastPictureUri; + if (targetUri != null) { + Uri thisUri = Images.Media.INTERNAL_CONTENT_URI; + if (thisUri != null) { + String bucket = thisUri.getQueryParameter("bucketId"); + if (bucket != null) { + targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", bucket).build(); + } + } + Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); + intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true); + intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true); + + try { + startActivity(intent); + } catch (android.content.ActivityNotFoundException ex) { + // ignore. + } } } @@ -1523,12 +1510,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol return true; } }); - menu.add(MenuHelper.VIDEO_SAVING_ITEM, MENU_SAVE_TAKE_NEW_VIDEO, 0, R.string.camera_takenewvideo).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - toss(); - return true; - } - }); } else { addBaseMenuItems(menu); MenuHelper.addImageMenuItems( @@ -1642,16 +1623,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol item.setIcon(android.R.drawable.ic_menu_preferences); } - private void showPostPictureAlert() { - mPostPictureAlert.setVisibility(View.VISIBLE); - mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, POST_PICTURE_ALERT_TIMEOUT); - } - - private void hidePostPictureAlert() { - cancelRestartPreviewTimeout(); - mPostPictureAlert.setVisibility(View.INVISIBLE); - } - private void cancelRestartPreviewTimeout() { mHandler.removeMessages(RESTART_PREVIEW); } diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 0c0f31b..0145d64 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -16,28 +16,77 @@ package com.android.camera; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Bundle; +import android.preference.ListPreference; import android.preference.PreferenceActivity; /** * CameraSettings */ public class CameraSettings extends PreferenceActivity + implements OnSharedPreferenceChangeListener { + public static final String KEY_VIDEO_QUALITY = "pref_camera_videoquality_key"; + public static final boolean DEFAULT_VIDEO_QUALITY_VALUE = true; + + private ListPreference mVideoQuality; + public CameraSettings() { } - protected int resourceId() { - return R.xml.camera_preferences; - } - /** Called with the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - addPreferencesFromResource(resourceId()); + addPreferencesFromResource(R.xml.camera_preferences); + + initUI(); + } + + @Override + protected void onResume() { + super.onResume(); + updateVideoQuality(); + } + + private void initUI() { + mVideoQuality = (ListPreference) findPreference(KEY_VIDEO_QUALITY); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + private void updateVideoQuality() { + boolean vidQualityValue = getBooleanPreference(mVideoQuality, DEFAULT_VIDEO_QUALITY_VALUE); + int vidQualityIndex = vidQualityValue ? 1 : 0; + String[] vidQualities = + getResources().getStringArray(R.array.pref_camera_videoquality_entries); + String vidQuality = vidQualities[vidQualityIndex]; + mVideoQuality.setSummary(vidQuality); + } + + private static int getIntPreference(ListPreference preference, int defaultValue) { + String s = preference.getValue(); + int result = defaultValue; + try { + result = Integer.parseInt(s); + } catch (NumberFormatException e) { + // Ignore, result is already the default value. + } + return result; + } + + private boolean getBooleanPreference(ListPreference preference, boolean defaultValue) { + return getIntPreference(preference, defaultValue ? 1 : 0) != 0; + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals(KEY_VIDEO_QUALITY)) { + updateVideoQuality(); + } } } diff --git a/src/com/android/camera/GalleryPicker.java b/src/com/android/camera/GalleryPicker.java index da946a0..df9049f 100644 --- a/src/com/android/camera/GalleryPicker.java +++ b/src/com/android/camera/GalleryPicker.java @@ -315,6 +315,9 @@ public class GalleryPicker extends Activity { String cameraBucketId = null; for (Map.Entry<String, String> entry: hashMap.entrySet()) { String key = entry.getKey(); + if (key == null) { + continue; + } if (key.equals(cameraItem)) { cameraBucketId = key; } else { @@ -651,9 +654,7 @@ public class GalleryPicker extends Activity { @Override public boolean onPrepareOptionsMenu(android.view.Menu menu) { - int keyboard = getResources().getConfiguration().keyboardHidden; - mFlipItem.setEnabled(keyboard == android.content.res.Configuration.KEYBOARDHIDDEN_YES); - + MenuHelper.setFlipOrientationEnabled(this, mFlipItem); return true; } diff --git a/src/com/android/camera/GalleryPickerItem.java b/src/com/android/camera/GalleryPickerItem.java index 3fc9678..c3b5df1 100644 --- a/src/com/android/camera/GalleryPickerItem.java +++ b/src/com/android/camera/GalleryPickerItem.java @@ -41,6 +41,7 @@ public class GalleryPickerItem extends ImageView { super(context, attrs, defStyle); mFrame = getResources().getDrawable(R.drawable.frame_gallery_preview); + mFrame.setCallback(this); } @Override diff --git a/src/com/android/camera/GallerySettings.java b/src/com/android/camera/GallerySettings.java index 8cbeba2..14cff3a 100644 --- a/src/com/android/camera/GallerySettings.java +++ b/src/com/android/camera/GallerySettings.java @@ -16,31 +16,24 @@ package com.android.camera; -import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.Preference; import android.preference.PreferenceActivity; -import android.content.Context; /** * GallerySettings */ -public class GallerySettings extends CameraSettings +public class GallerySettings extends PreferenceActivity { public GallerySettings() { } - - @Override - protected int resourceId() { - return R.xml.gallery_preferences; - } - + /** Called with the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + addPreferencesFromResource(R.xml.gallery_preferences); } } diff --git a/src/com/android/camera/ImageGallery2.java b/src/com/android/camera/ImageGallery2.java index 44d297b..92e9c57 100644 --- a/src/com/android/camera/ImageGallery2.java +++ b/src/com/android/camera/ImageGallery2.java @@ -113,14 +113,15 @@ public class ImageGallery2 extends Activity { if (mSelectedImageGetter.getCurrentImage() == null) return; - menu.add(0, 0, 0, R.string.view).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - mGvs.onSelect(mGvs.mCurrentSelection); - return true; - } - }); - boolean isImage = ImageManager.isImage(mSelectedImageGetter.getCurrentImage()); + if (isImage) { + menu.add(0, 0, 0, R.string.view).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + mGvs.onSelect(mGvs.mCurrentSelection); + return true; + } + }); + } menu.setHeaderTitle(isImage ? R.string.context_menu_header : R.string.video_context_menu_header); @@ -138,6 +139,7 @@ public class ImageGallery2 extends Activity { mGvs.clearCache(); mGvs.invalidate(); + mGvs.requestLayout(); mGvs.start(); mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE : View.VISIBLE); } @@ -187,8 +189,9 @@ public class ImageGallery2 extends Activity { mGvs.clearCache(); mAllImages.removeImage(mSelectedImageGetter.getCurrentImage()); mGvs.invalidate(); + mGvs.requestLayout(); mGvs.start(); - mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE : View.VISIBLE); + mNoImagesView.setVisibility(mAllImages.isEmpty() ? View.VISIBLE : View.GONE); } }; @@ -217,6 +220,7 @@ public class ImageGallery2 extends Activity { private Runnable mLongPressCallback = new Runnable() { public void run() { + mGvs.select(-2, false); mGvs.showContextMenu(); } }; @@ -224,6 +228,7 @@ public class ImageGallery2 extends Activity { @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + mGvs.select(-2, false); // The keyUp doesn't get called when the longpress menu comes up. We only get here when the user // lets go of the center key before the longpress menu comes up. mHandler.removeCallbacks(mLongPressCallback); @@ -243,6 +248,7 @@ public class ImageGallery2 extends Activity { int sel = mGvs.mCurrentSelection; int columns = mGvs.mCurrentSpec.mColumns; int count = mAllImages.getCount(); + boolean pressed = false; if (mGvs.mShowSelection) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_RIGHT: @@ -266,6 +272,7 @@ public class ImageGallery2 extends Activity { } break; case KeyEvent.KEYCODE_DPAD_CENTER: + pressed = true; mHandler.postDelayed(mLongPressCallback, ViewConfiguration.getLongPressTimeout()); break; case KeyEvent.KEYCODE_DEL: @@ -300,7 +307,7 @@ public class ImageGallery2 extends Activity { } } if (handled) { - mGvs.select(sel); + mGvs.select(sel, pressed); return true; } else @@ -368,13 +375,15 @@ public class ImageGallery2 extends Activity { super.onPause(); mPausing = true; stopCheckingThumbnails(); - mAllImages.deactivate(); mGvs.onPause(); if (mReceiver != null) { unregisterReceiver(mReceiver); mReceiver = null; } + // Now that we've paused the threads that are using the cursor it is safe + // to deactivate it. + mAllImages.deactivate(); } private void rebake(boolean unmounted, boolean scanning) { @@ -567,8 +576,8 @@ public class ImageGallery2 extends Activity { if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { mSlideShowItem = addSlideShowMenu(menu, 5); - mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); } + mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); item = menu.add(0, 0, 1000, R.string.camerasettings); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @@ -588,14 +597,11 @@ public class ImageGallery2 extends Activity { @Override public boolean onPrepareOptionsMenu(android.view.Menu menu) { if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { - boolean imageSelected = isImageSelected(); boolean videoSelected = isVideoSelected(); - int keyboard = getResources().getConfiguration().keyboardHidden; - mFlipItem.setEnabled(imageSelected - && (keyboard == android.content.res.Configuration.KEYBOARDHIDDEN_YES)); // TODO: Only enable slide show if there is at least one image in the folder. mSlideShowItem.setEnabled(!videoSelected); } + MenuHelper.setFlipOrientationEnabled(this, mFlipItem); return true; } @@ -685,6 +691,7 @@ public class ImageGallery2 extends Activity { private LayoutSpec mCurrentSpec; private boolean mShowSelection = false; private int mCurrentSelection = -1; + private boolean mCurrentSelectionPressed; private boolean mDirectionBiasDown = true; private final static boolean sDump = false; @@ -748,9 +755,9 @@ public class ImageGallery2 extends Activity { int pos = computeSelectedIndex(e); if (pos >= 0 && pos < mGallery.mAllImages.getCount()) { - select(pos); + select(pos, true); } else { - select(-1); + select(-1, false); } if (mImageBlockManager != null) mImageBlockManager.repaintSelection(mCurrentSelection); @@ -766,7 +773,7 @@ public class ImageGallery2 extends Activity { else if (velocityY < -maxVelocity) velocityY = -maxVelocity; - select(-1); + select(-1, false); if (mFling) { mScroller = new Scroller(getContext()); mScroller.fling(0, mScrollY, 0, -(int)velocityY, 0, 0, 0, mMaxScrollY); @@ -782,7 +789,7 @@ public class ImageGallery2 extends Activity { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - select(-1); + select(-1, false); scrollBy(0, (int)distanceY); invalidate(); return true; @@ -795,6 +802,7 @@ public class ImageGallery2 extends Activity { @Override public boolean onSingleTapUp(MotionEvent e) { + select(mCurrentSelection, false); int index = computeSelectedIndex(e); if (index >= 0 && index < mGallery.mAllImages.getCount()) { onSelect(index); @@ -834,13 +842,22 @@ public class ImageGallery2 extends Activity { invalidate(); } - public void select(int newSel) { + /** + * + * @param newSel -2 means use old selection, -1 means remove selection + * @param newPressed + */ + public void select(int newSel, boolean newPressed) { + if (newSel == -2) { + newSel = mCurrentSelection; + } int oldSel = mCurrentSelection; - if (oldSel == newSel) + if ((oldSel == newSel) && (mCurrentSelectionPressed == newPressed)) return; mShowSelection = (newSel != -1); mCurrentSelection = newSel; + mCurrentSelectionPressed = newPressed; if (mImageBlockManager != null) { mImageBlockManager.repaintSelection(oldSel); mImageBlockManager.repaintSelection(newSel); @@ -1527,11 +1544,16 @@ public class ImageGallery2 extends Activity { } private void paintSel(int pos, int xPos, int yPos) { + int[] stateSet = EMPTY_STATE_SET; if (pos == mCurrentSelection && mShowSelection) { - mCellOutline.setState(ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET); - } else { - mCellOutline.setState(EMPTY_STATE_SET); + if (mCurrentSelectionPressed) { + stateSet = PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; + } else { + stateSet = ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; + } } + + mCellOutline.setState(stateSet); mCanvas.setBitmap(mBitmap); mCellOutline.setBounds(xPos, yPos, xPos+mCurrentSpec.mCellWidth, yPos+mCurrentSpec.mCellHeight); mCellOutline.draw(mCanvas); diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java index 1fe93b5..4ceedb8 100755 --- a/src/com/android/camera/ImageManager.java +++ b/src/com/android/camera/ImageManager.java @@ -62,8 +62,17 @@ import java.util.HashMap; * */ public class ImageManager { - public static final String CAMERA_IMAGE_BUCKET_NAME = "/sdcard/dcim/camera"; - public static final String CAMERA_IMAGE_BUCKET_ID = String.valueOf(CAMERA_IMAGE_BUCKET_NAME.hashCode()); + public static final String CAMERA_IMAGE_BUCKET_NAME = + Environment.getExternalStorageDirectory().toString() + "/dcim/Camera"; + public static final String CAMERA_IMAGE_BUCKET_ID = getBucketId(CAMERA_IMAGE_BUCKET_NAME); + + /** + * Matches code in MediaProvider.computeBucketValues. Should be a common function. + */ + + public static String getBucketId(String path) { + return String.valueOf(path.toLowerCase().hashCode()); + } // To enable verbose logging for this class, change false to true. The other logic ensures that // this logging can be disabled by turned off DEBUG and lower, and that it can be enabled by @@ -1431,7 +1440,7 @@ public class ImageManager { requery(); } } - return false; + return true; } @@ -1716,6 +1725,11 @@ public class ImageManager { */ public abstract IImage getImageForUri(Uri uri);; + /** + * + * @param image + * @return true if the image was removed. + */ public abstract boolean removeImage(IImage image); /** * Removes the image at the ith position. @@ -2252,6 +2266,7 @@ public class ImageManager { protected int indexDisplayName() { return -1; } protected int indexThumbId() { return INDEX_THUMB_ID; } + @Override protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { return new Image(id, miniThumbId, mContentResolver, this, index, rotation); } @@ -2627,12 +2642,20 @@ public class ImageManager { } public boolean removeImage(IImage image) { + IImageList parent = image.getContainer(); int pos = -1; + int baseIndex = 0; while (++pos < mSubList.length) { IImageList sub = mSubList[pos]; - if (sub.removeImage(image)) { - return true; + if (sub == parent) { + if (sub.removeImage(image)) { + modifySkipCountForDeletedImage(baseIndex); + return true; + } else { + break; + } } + baseIndex += sub.getCount(); } return false; } @@ -3303,8 +3326,9 @@ public class ImageManager { protected int indexDisplayName() { return -1; } protected int indexThumbId() { return INDEX_THUMB_ID; } + @Override protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, - long timestamp, int index) { + long timestamp, int index, int rotation) { return new VideoObject(id, miniThumbId, mContentResolver, this, timestamp, index); } @@ -3572,24 +3596,13 @@ public class ImageManager { return sInstance; } + static public byte [] miniThumbData(Bitmap source) { if (source == null) return null; - float scale; - if (source.getWidth() < source.getHeight()) { - scale = MINI_THUMB_TARGET_SIZE / (float)source.getWidth(); - } else { - scale = MINI_THUMB_TARGET_SIZE / (float)source.getHeight(); - } - Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); - Bitmap miniThumbnail = ImageLoader.transform(matrix, source, - MINI_THUMB_TARGET_SIZE, MINI_THUMB_TARGET_SIZE, false); - - if (miniThumbnail != source) { - source.recycle(); - } + Bitmap miniThumbnail = extractMiniThumb(source, MINI_THUMB_TARGET_SIZE, + MINI_THUMB_TARGET_SIZE); java.io.ByteArrayOutputStream miniOutStream = new java.io.ByteArrayOutputStream(); miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream); miniThumbnail.recycle(); @@ -3604,6 +3617,33 @@ public class ImageManager { return null; } + /** + * Creates a centered bitmap of the desired size. Recycles the input. + * @param source + * @return + */ + static public Bitmap extractMiniThumb(Bitmap source, int width, int height) { + if (source == null) { + return null; + } + + float scale; + if (source.getWidth() < source.getHeight()) { + scale = width / (float)source.getWidth(); + } else { + scale = height / (float)source.getHeight(); + } + Matrix matrix = new Matrix(); + matrix.setScale(scale, scale); + Bitmap miniThumbnail = ImageLoader.transform(matrix, source, + width, height, false); + + if (miniThumbnail != source) { + source.recycle(); + } + return miniThumbnail; + } + static Bitmap rotate(Bitmap b, int degrees) { if (degrees != 0 && b != null) { Matrix m = new Matrix(); diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java index 9e4fb82..f8e1658 100644 --- a/src/com/android/camera/MenuHelper.java +++ b/src/com/android/camera/MenuHelper.java @@ -19,11 +19,15 @@ package com.android.camera; 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.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Handler; import android.provider.MediaStore; +import android.provider.MediaStore.Images; import android.util.Config; import android.util.Log; import android.view.Menu; @@ -266,18 +270,107 @@ public class MenuHelper { } finally { } - ((TextView)d.findViewById(R.id.details_attrname_1)).setText(R.string.details_file_size); - ((TextView)d.findViewById(R.id.details_attrvalu_1)).setText(lengthString); + ((TextView)d.findViewById(R.id.details_file_size_value)) + .setText(lengthString); + int dimensionWidth = 0; + int dimensionHeight = 0; if (isImage) { - String dimensionsString = String.valueOf(image.getWidth() + " X " + image.getHeight()); - ((TextView)d.findViewById(R.id.details_attrname_2)).setText(R.string.details_image_resolution); - ((TextView)d.findViewById(R.id.details_attrvalu_2)).setText(dimensionsString); + dimensionWidth = image.getWidth(); + dimensionHeight = image.getHeight(); + d.findViewById(R.id.details_duration_row).setVisibility(View.GONE); + d.findViewById(R.id.details_frame_rate_row).setVisibility(View.GONE); + d.findViewById(R.id.details_bit_rate_row).setVisibility(View.GONE); + d.findViewById(R.id.details_format_row).setVisibility(View.GONE); + d.findViewById(R.id.details_codec_row).setVisibility(View.GONE); } else { - d.findViewById(R.id.details_attrname_2).setVisibility(View.GONE); - d.findViewById(R.id.details_attrvalu_2).setVisibility(View.GONE); + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY); + retriever.setDataSource(image.getDataPath()); + try { + dimensionWidth = Integer.parseInt( + retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); + dimensionHeight = Integer.parseInt( + retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); + } catch (NumberFormatException e) { + dimensionWidth = 0; + dimensionHeight = 0; + } + + try { + long durationMs = Long.parseLong(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); + } + ((TextView)d.findViewById(R.id.details_duration_value)) + .setText(durationValue); + } catch (NumberFormatException e) { + d.findViewById(R.id.details_frame_rate_row) + .setVisibility(View.GONE); + } + + try { + String frame_rate = String.format( + activity.getString(R.string.details_fps), + Integer.parseInt( + retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_FRAME_RATE))); + ((TextView)d.findViewById(R.id.details_frame_rate_value)) + .setText(frame_rate); + } catch (NumberFormatException e) { + d.findViewById(R.id.details_frame_rate_row) + .setVisibility(View.GONE); + } + + try { + long bitRate = Long.parseLong(retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_BIT_RATE)); + String bps; + if (bitRate < 1000000) { + bps = String.format( + activity.getString(R.string.details_kbps), + bitRate / 1000); + } else { + bps = String.format( + activity.getString(R.string.details_mbps), + ((double) bitRate) / 1000000.0); + } + ((TextView)d.findViewById(R.id.details_bit_rate_value)) + .setText(bps); + } catch (NumberFormatException e) { + d.findViewById(R.id.details_bit_rate_row) + .setVisibility(View.GONE); + } + + String format = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_FORMAT); + ((TextView)d.findViewById(R.id.details_format_value)) + .setText(format); + + String codec = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_CODEC); + ((TextView)d.findViewById(R.id.details_codec_value)) + .setText(codec); } + String dimensionsString = String.format( + activity.getString(R.string.details_dimension_x), + dimensionWidth, dimensionHeight); + ((TextView)d.findViewById(R.id.details_resolution_value)) + .setText(dimensionsString); + String dateString = ""; long dateTaken = image.getDateTaken(); if (dateTaken != 0) { @@ -285,12 +378,19 @@ public class MenuHelper { java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat(); dateString = dateFormat.format(date); - ((TextView)d.findViewById(R.id.details_attrname_3)).setText(R.string.details_date_taken); - ((TextView)d.findViewById(R.id.details_attrvalu_3)).setText(dateString); + ((TextView)d.findViewById(R.id.details_date_taken_value)) + .setText(dateString); } else { - d.findViewById(R.id.details_daterow).setVisibility(View.GONE); + d.findViewById(R.id.details_date_taken_row) + .setVisibility(View.GONE); } + builder.setNeutralButton(R.string.details_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); builder.setIcon(android.R.drawable.ic_dialog_info) .setTitle(R.string.details_panel_title) @@ -356,7 +456,9 @@ public class MenuHelper { } static void deleteImage(Activity activity, Runnable onDelete, IImage image) { - deleteImageImpl(activity, onDelete, ImageManager.isImage(image)); + if (image != null) { + deleteImageImpl(activity, onDelete, ImageManager.isImage(image)); + } } private static void deleteImageImpl(Activity activity, final Runnable onDelete, boolean isPhoto) { @@ -365,33 +467,38 @@ public class MenuHelper { if (onDelete != null) onDelete.run(); } else { - android.app.AlertDialog.Builder b = new android.app.AlertDialog.Builder(activity); - b.setIcon(android.R.drawable.ic_dialog_alert); - b.setTitle(R.string.confirm_delete_title); - b.setMessage(isPhoto? R.string.confirm_delete_message - : R.string.confirm_delete_video_message); - b.setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { - public void onClick(android.content.DialogInterface v, int x) { - if (onDelete != null) - onDelete.run(); - } - }); - b.setNegativeButton(android.R.string.cancel, new android.content.DialogInterface.OnClickListener() { - public void onClick(android.content.DialogInterface v, int x) { - - } - }); - b.create().show(); + displayDeleteDialog(activity, onDelete, isPhoto); } } + public static void displayDeleteDialog(Activity activity, + final Runnable onDelete, boolean isPhoto) { + android.app.AlertDialog.Builder b = new android.app.AlertDialog.Builder(activity); + b.setIcon(android.R.drawable.ic_dialog_alert); + b.setTitle(R.string.confirm_delete_title); + b.setMessage(isPhoto? R.string.confirm_delete_message + : R.string.confirm_delete_video_message); + b.setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { + public void onClick(android.content.DialogInterface v, int x) { + if (onDelete != null) + onDelete.run(); + } + }); + b.setNegativeButton(android.R.string.cancel, new android.content.DialogInterface.OnClickListener() { + public void onClick(android.content.DialogInterface v, int x) { + + } + }); + b.create().show(); + } + static void addSwitchModeMenuItem(Menu menu, final Activity activity, final boolean switchToVideo) { int group = switchToVideo ? MenuHelper.IMAGE_MODE_ITEM : MenuHelper.VIDEO_MODE_ITEM; int labelId = switchToVideo ? R.string.switch_to_video_lable : R.string.switch_to_camera_lable; int iconId = switchToVideo ? R.drawable.ic_menu_camera_video_view - : R.drawable.ic_menu_camera; + : android.R.drawable.ic_menu_camera; MenuItem item = menu.add(group, MENU_SWITCH_CAMERA_MODE, 0, labelId).setOnMenuItemClickListener( new OnMenuItemClickListener() { @@ -408,6 +515,26 @@ public class MenuHelper { item.setIcon(iconId); } + static void gotoStillImageCapture(Activity activity) { + Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + try { + activity.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Could not start still image capture activity", e); + } + } + + static void gotoCameraImageGallery(Activity activity) { + Uri target = Images.Media.INTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("bucketId", + ImageManager.CAMERA_IMAGE_BUCKET_ID).build(); + Intent intent = new Intent(Intent.ACTION_VIEW, target); + try { + activity.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Could not start gallery activity", e); + } + } + static void addCaptureMenuItems(Menu menu, final Activity activity) { menu.add(0, MENU_CAPTURE_PICTURE, 1, R.string.capture_picture) @@ -423,7 +550,7 @@ public class MenuHelper { return true; } }) - .setIcon(R.drawable.ic_menu_camera); + .setIcon(android.R.drawable.ic_menu_camera); menu.add(0, MENU_CAPTURE_VIDEO, 2, R.string.capture_video) .setOnMenuItemClickListener( @@ -473,5 +600,10 @@ public class MenuHelper { ? android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER : req); } + + static void setFlipOrientationEnabled(Activity activity, MenuItem flipItem) { + int keyboard = activity.getResources().getConfiguration().hardKeyboardHidden; + flipItem.setEnabled(keyboard != android.content.res.Configuration.HARDKEYBOARDHIDDEN_NO); + } } diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index 7fcab9a..9da1f49 100644 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -17,11 +17,11 @@ package com.android.camera; import java.io.File; -import java.util.ArrayList; import java.io.IOException; +import java.util.ArrayList; import android.app.Activity; -import android.app.ProgressDialog; +import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -30,7 +30,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; import android.location.LocationManager; @@ -44,16 +43,14 @@ import android.os.Message; import android.os.StatFs; import android.os.SystemClock; import android.preference.PreferenceManager; -import android.provider.MediaStore.Images; +import android.provider.MediaStore; import android.provider.MediaStore.Video; import android.text.format.DateFormat; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -62,40 +59,32 @@ import android.view.animation.Animation; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; -import android.widget.RelativeLayout.LayoutParams; public class VideoCamera extends Activity implements View.OnClickListener, SurfaceHolder.Callback { private static final String TAG = "videocamera"; private static final boolean DEBUG = true; - private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = 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 int KEEP = 2; private static final int CLEAR_SCREEN_DELAY = 4; private static final int UPDATE_RECORD_TIME = 5; - private static final int RESTART_PREVIEW = 6; private static final int SCREEN_DELAY = 2 * 60 * 1000; - private static final int POST_PICTURE_ALERT_TIMEOUT = 6 * 1000; - private static final int NO_STORAGE_ERROR = -1; - private static final int CANNOT_STAT_ERROR = -2; + private static final long NO_STORAGE_ERROR = -1L; + private static final long CANNOT_STAT_ERROR = -2L; + private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L; - public static final int MENU_SWITCH_TO_VIDEO = 0; - public static final int MENU_SWITCH_TO_CAMERA = 1; public static final int MENU_SETTINGS = 6; public static final int MENU_GALLERY_PHOTOS = 7; public static final int MENU_GALLERY_VIDEOS = 8; - public static final int MENU_SAVE_SELECT_PHOTOS = 30; - public static final int MENU_SAVE_NEW_PHOTO = 31; - public static final int MENU_SAVE_SELECTVIDEO = 32; - public static final int MENU_SAVE_TAKE_NEW_VIDEO = 33; public static final int MENU_SAVE_GALLERY_PHOTO = 34; - public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35; - public static final int MENU_SAVE_CAMERA_DONE = 36; - public static final int MENU_SAVE_CAMERA_VIDEO_DONE = 37; + public static final int MENU_SAVE_PLAY_VIDEO = 35; + public static final int MENU_SAVE_SELECT_VIDEO = 36; + public static final int MENU_SAVE_NEW_VIDEO = 37; Toast mToast; SharedPreferences mPreferences; @@ -109,9 +98,12 @@ 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; private String mCurrentVideoFilename; private Uri mCurrentVideoUri; + private ContentValues mCurrentVideoValues; boolean mPausing = false; @@ -129,8 +121,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa View mPostPictureAlert; LocationManager mLocationManager = null; - private int mPicturesRemaining; - private Handler mHandler = new MainHandler(); private void cancelSavingNotification() { @@ -177,15 +167,17 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } String text = minutesString + ":" + secondsString; mRecordingTimeView.setText(text); + // Work around a limitation of the T-Mobile G1: The T-Mobile + // hardware blitter can't pixel-accurately scale and clip at the same time, + // and the SurfaceFlinger doesn't attempt to work around this limitation. + // In order to avoid visual corruption we must manually refresh the entire + // surface view when changing any overlapping view's contents. + mVideoPreview.invalidate(); mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000); } break; } - case RESTART_PREVIEW: - hideVideoFrameAndStartPreview(); - break; - default: Log.v(TAG, "Unhandled message: " + msg.what); break; @@ -254,16 +246,12 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); mPostPictureAlert = findViewById(R.id.post_picture_panel); - View b; - - b = findViewById(R.id.play); - b.setOnClickListener(this); - b = findViewById(R.id.share); - b.setOnClickListener(this); - - b = findViewById(R.id.discard); - b.setOnClickListener(this); + int[] ids = new int[]{R.id.play, R.id.share, R.id.discard, + R.id.capture, R.id.cancel, R.id.accept}; + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } mModeIndicatorView = (ImageView) findViewById(R.id.mode_indicator); mRecordingIndicatorView = (ImageView) findViewById(R.id.recording_indicator); @@ -281,7 +269,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa Thread t = new Thread(new Runnable() { public void run() { - final boolean storageOK = calculatePicturesRemaining() > 0; + final boolean storageOK = getAvailableStorage() >= LOW_STORAGE_THRESHOLD; if (hintView == null) return; @@ -315,12 +303,20 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa public void onClick(View v) { switch (v.getId()) { - case R.id.discard: { - File f = new File(mCurrentVideoFilename); - f.delete(); - mContentResolver.delete(mCurrentVideoUri, null, null); + case R.id.capture: + doDiscardCurrentVideo(); + break; - hideVideoFrameAndStartPreview(); + case R.id.accept: + doReturnToPicker(true); + break; + + case R.id.cancel: + doReturnToPicker(false); + break; + + case R.id.discard: { + doDiscardCurrentVideo(); break; } @@ -339,29 +335,35 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } case R.id.play: { - Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); - try { - startActivity(intent); - } catch (android.content.ActivityNotFoundException ex) { - Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); - } + doPlayCurrentVideo(); break; } } } + private void doPlayCurrentVideo() { + Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); + try { + startActivity(intent); + } catch (android.content.ActivityNotFoundException ex) { + Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); + } + } + + private void doDiscardCurrentVideo() { + deleteCurrentVideo(); + hideVideoFrameAndStartPreview(); + } + private void showStorageToast() { - String noStorageText = null; - int remaining = calculatePicturesRemaining(); + long remaining = getAvailableStorage(); 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(); + Toast.makeText(this, getString(R.string.no_storage), Toast.LENGTH_LONG).show(); + } else if (remaining < LOW_STORAGE_THRESHOLD) { + new AlertDialog.Builder(this).setTitle(R.string.spaceIsLow_title) + .setMessage(R.string.spaceIsLow_content) + .show(); } } @@ -456,12 +458,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa return super.onKeyDown(keyCode, event); } - @Override - public boolean onTrackballEvent(MotionEvent event) { - cancelRestartPreviewTimeout(); - return super.onTrackballEvent(event); - } - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { stopVideoRecording(); initializeVideo(); @@ -511,7 +507,6 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - addBaseMenuItems(menu); MenuHelper.addImageMenuItems( menu, @@ -545,26 +540,47 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } }); gallery.setIcon(android.R.drawable.ic_menu_gallery); + return true; } - private int calculatePicturesRemaining() { + private boolean isPickIntent() { + String action = getIntent().getAction(); + return (Intent.ACTION_PICK.equals(action) || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); + } + + private void doReturnToPicker(boolean success) { + Intent resultIntent = new Intent(); + int resultCode; + if (success) { + resultCode = RESULT_OK; + resultIntent.setData(mCurrentVideoUri); + } else { + resultCode = RESULT_CANCELED; + } + setResult(resultCode, resultIntent); + finish(); + } + + /** + * Returns + * @return number of bytes available, or an ERROR code. + */ + private static long getAvailableStorage() { try { if (!ImageManager.hasStorage()) { - mPicturesRemaining = NO_STORAGE_ERROR; + return 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; + return ((long)stat.getAvailableBlocks() * (long)stat.getBlockSize()); } } 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 + // free bytes exist. It might be zero but just leave it // blank since we really don't know. - mPicturesRemaining = CANNOT_STAT_ERROR; + return CANNOT_STAT_ERROR; } - return mPicturesRemaining; } private void initializeVideo() { @@ -577,6 +593,8 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } mMediaRecorder = new MediaRecorder(); + mNeedToDeletePartialRecording = true; + mNeedToRegisterRecording = false; if (DEBUG_SUPPRESS_AUDIO_RECORDING) { Log.v(TAG, "DEBUG_SUPPRESS_AUDIO_RECORDING is true."); @@ -588,17 +606,26 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa Log.v(TAG, "before setOutputFile"); createVideoPath(); mMediaRecorder.setOutputFile(mCurrentVideoFilename); - Boolean videoQualityLow = getIntPreference("pref_camera_videoquality_key") == 0; + boolean videoQualityHigh = getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY, + CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); + + { + Intent intent = getIntent(); + if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { + int extraVideoQuality = intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); + videoQualityHigh = (extraVideoQuality > 0); + } + } // Use the same frame rate for both, since internally // if the frame rate is too large, it can cause camera to become // unstable. We need to fix the MediaRecorder to disable the support // of setting frame rate for now. mMediaRecorder.setVideoFrameRate(20); - if (videoQualityLow) { - mMediaRecorder.setVideoSize(176,144); - } else { + if (videoQualityHigh) { mMediaRecorder.setVideoSize(352,288); + } else { + mMediaRecorder.setVideoSize(176,144); } mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); if (!DEBUG_SUPPRESS_AUDIO_RECORDING) { @@ -641,17 +668,29 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } } - private int getIntPreference(String key) { - String s = mPreferences.getString(key, "0"); - return Integer.parseInt(s); + private int getIntPreference(String key, int defaultValue) { + String s = mPreferences.getString(key, ""); + int result = defaultValue; + try { + result = Integer.parseInt(s); + } catch (NumberFormatException e) { + // Ignore, result is already the default value. + } + return result; + } + + private boolean getBooleanPreference(String key, boolean defaultValue) { + return getIntPreference(key, defaultValue ? 1 : 0) != 0; } private void createVideoPath() { long dateTaken = System.currentTimeMillis(); String title = createName(dateTaken); String displayName = title + ".3gp"; // Used when emailing. - String filename = ImageManager.CAMERA_IMAGE_BUCKET_NAME + "/" - + Long.toString(dateTaken) + ".3gp"; + String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; + File cameraDir = new File(cameraDirPath); + cameraDir.mkdirs(); + String filename = cameraDirPath + "/" + Long.toString(dateTaken) + ".3gp"; ContentValues values = new ContentValues(7); values.put(Video.Media.TITLE, title); values.put(Video.Media.DISPLAY_NAME, displayName); @@ -659,10 +698,31 @@ 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); - Uri videoTable = Uri.parse("content://media/external/video/media"); - Uri item = mContentResolver.insert(videoTable, values); mCurrentVideoFilename = filename; - mCurrentVideoUri = item; + mCurrentVideoValues = values; + mCurrentVideoUri = null; + } + + private void registerVideo() { + Uri videoTable = Uri.parse("content://media/external/video/media"); + mCurrentVideoUri = mContentResolver.insert(videoTable, + mCurrentVideoValues); + 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); + } + mCurrentVideoFilename = null; + } + if (mCurrentVideoUri != null) { + mContentResolver.delete(mCurrentVideoUri, null, null); + mCurrentVideoUri = null; + } } private void addBaseMenuItems(Menu menu) { @@ -734,20 +794,20 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa } private void showPostRecordingAlert() { - cancelRestartPreviewTimeout(); + boolean isPick = isPickIntent(); + 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.accept).setVisibility(pickVisible); + mPostPictureAlert.findViewById(R.id.cancel).setVisibility(pickVisible); mPostPictureAlert.setVisibility(View.VISIBLE); - mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, POST_PICTURE_ALERT_TIMEOUT); } private void hidePostPictureAlert() { - cancelRestartPreviewTimeout(); mPostPictureAlert.setVisibility(View.INVISIBLE); } - private void cancelRestartPreviewTimeout() { - mHandler.removeMessages(RESTART_PREVIEW); - } - private boolean isPostRecordingAlertVisible() { return mPostPictureAlert.getVisibility() == View.VISIBLE; } @@ -757,14 +817,24 @@ public class VideoCamera extends Activity implements View.OnClickListener, Surfa if (mMediaRecorderRecording || mMediaRecorder != null) { if (mMediaRecorderRecording) { mMediaRecorder.stop(); + mNeedToDeletePartialRecording = false; + mNeedToRegisterRecording = true; + mMediaRecorderRecording = false; } releaseMediaRecorder(); - mMediaRecorderRecording = false; mModeIndicatorView.setVisibility(View.VISIBLE); mRecordingIndicatorView.setVisibility(View.GONE); mRecordingTimeView.setVisibility(View.GONE); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + if (mNeedToRegisterRecording) { + registerVideo(); + mNeedToRegisterRecording = false; + } + if (mNeedToDeletePartialRecording){ + deleteCurrentVideo(); + mNeedToDeletePartialRecording = false; + } } private void hideVideoFrameAndStartPreview() { diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java index 9579f19..d1ef142 100644 --- a/src/com/android/camera/ViewImage.java +++ b/src/com/android/camera/ViewImage.java @@ -21,6 +21,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Matrix; import android.net.Uri; @@ -49,6 +50,7 @@ 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; @@ -56,7 +58,7 @@ import com.android.camera.ImageManager.IImage; import java.util.Random; -public class ViewImage extends Activity +public class ViewImage extends Activity implements View.OnClickListener { static final String TAG = "ViewImage"; private ImageGetter mGetter; @@ -81,6 +83,9 @@ public class ViewImage extends Activity private static final int MODE_NORMAL = 1; private static final int MODE_SLIDESHOW = 2; private int mMode = MODE_NORMAL; + private boolean mFullScreenInNormalMode; + private boolean mShowActionIcons; + private View mActionIconPanel; private boolean mSortAscending = false; private int mSlideShowInterval; @@ -652,8 +657,7 @@ public class ViewImage extends Activity mImageMenuRunnable.gettingReadyToOpen(menu, mAllImages.getImageAt(mCurrentPosition)); } - int keyboard = getResources().getConfiguration().keyboardHidden; - mFlipItem.setEnabled(keyboard == android.content.res.Configuration.KEYBOARDHIDDEN_YES); + MenuHelper.setFlipOrientationEnabled(this, mFlipItem); menu.findItem(MenuHelper.MENU_IMAGE_SHARE).setEnabled(isCurrentImageShareable()); @@ -1067,6 +1071,12 @@ public class ViewImage extends Activity mSlideShowImageViews[i].setVisibility(View.INVISIBLE); } + mActionIconPanel = findViewById(R.id.action_icon_panel); + int[] ids = {R.id.capture, R.id.gallery, R.id.discard, R.id.share, R.id.setas}; + for(int id : ids) { + findViewById(id).setOnClickListener(this); + } + Uri uri = getIntent().getData(); if (Config.LOGV) @@ -1081,12 +1091,24 @@ public class ViewImage extends Activity 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(); + boolean slideShow = b != null ? b.getBoolean("slideshow", false) : false; if (slideShow) { setMode(MODE_SLIDESHOW); loadNextImage(mCurrentPosition, 0, true); + } else { + if (mFullScreenInNormalMode) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + if (mShowActionIcons) { + mActionIconPanel.setVisibility(View.VISIBLE); + } } // Get the zoom controls and add them to the bottom of the map @@ -1102,7 +1124,20 @@ public class ViewImage extends Activity mNextImageView = findViewById(R.id.next_image); mPrevImageView = findViewById(R.id.prev_image); - MenuHelper.requestOrientation(this, mPrefs); + setOrientation(); + } + + private void setOrientation() { + Intent intent = getIntent(); + if (intent.hasExtra(MediaStore.EXTRA_SCREEN_ORIENTATION)) { + int orientation = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, + ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + if (orientation != getRequestedOrientation()) { + setRequestedOrientation(orientation); + } + } else { + MenuHelper.requestOrientation(this, mPrefs); + } } private Animation makeInAnimation(int id) { @@ -1134,6 +1169,7 @@ public class ViewImage extends Activity for (ImageViewTouchBase ivt: mImageViews) { ivt.clear(); } + mActionIconPanel.setVisibility(View.GONE); if (false) { Log.v(TAG, "current is " + this.mSlideShowImageCurrent); @@ -1170,8 +1206,12 @@ public class ViewImage extends Activity } else { if (Config.LOGV) Log.v(TAG, "slide show mode off, mCurrentPosition == " + mCurrentPosition); - win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + win.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (mFullScreenInNormalMode) { + win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } if (mGetter != null) mGetter.cancelCurrent(); @@ -1179,6 +1219,9 @@ public class ViewImage extends Activity if (sSlideShowHidesStatusBar) { win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } + if (mShowActionIcons) { + mActionIconPanel.setVisibility(View.VISIBLE); + } ImageViewTouchBase dst = mImageViews[1]; dst.mLastXTouchPos = -1; @@ -1403,7 +1446,7 @@ public class ViewImage extends Activity }); setImage(mCurrentPosition); - MenuHelper.requestOrientation(this, mPrefs); + setOrientation(); } @Override @@ -1433,4 +1476,49 @@ public class ViewImage extends Activity public void onStop() { super.onStop(); } + + public void onClick(View v) { + switch (v.getId()) { + + case R.id.capture: { + MenuHelper.gotoStillImageCapture(this); + } + break; + + case R.id.gallery: { + MenuHelper.gotoCameraImageGallery(this); + } + break; + + case R.id.discard: { + MenuHelper.displayDeleteDialog(this, mDeletePhotoRunnable, true); + } + break; + + case R.id.share: { + Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri(); + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("image/jpeg"); + intent.putExtra(Intent.EXTRA_STREAM, u); + try { + startActivity(Intent.createChooser(intent, getText(R.string.sendImage))); + } catch (android.content.ActivityNotFoundException ex) { + Toast.makeText(this, R.string.no_way_to_share_image, Toast.LENGTH_SHORT).show(); + } + } + break; + + case R.id.setas: { + Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri(); + Intent intent = new Intent(Intent.ACTION_ATTACH_DATA, u); + try { + startActivity(Intent.createChooser(intent, getText(R.string.setImage))); + } catch (android.content.ActivityNotFoundException ex) { + Toast.makeText(this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); + } + } + break; + } + } } diff --git a/src/com/android/camera/Wallpaper.java b/src/com/android/camera/Wallpaper.java index ff41242..1cc12fb 100644 --- a/src/com/android/camera/Wallpaper.java +++ b/src/com/android/camera/Wallpaper.java @@ -74,14 +74,16 @@ public class Wallpaper extends Activity { }; static class SetWallpaperThread extends Thread { - private Bitmap mBitmap; - private Handler mHandler; - private Context mContext; + private final Bitmap mBitmap; + private final Handler mHandler; + private final Context mContext; + private final File mFile; - public SetWallpaperThread(Bitmap bitmap, Handler handler, Context context) { + public SetWallpaperThread(Bitmap bitmap, Handler handler, Context context, File file) { mBitmap = bitmap; mHandler = handler; mContext = context; + mFile = file; } @Override @@ -92,6 +94,7 @@ public class Wallpaper extends Activity { Log.e(LOG_TAG, "Failed to set wallpaper.", e); } finally { mHandler.sendEmptyMessage(FINISH); + mFile.delete(); } } } @@ -148,6 +151,10 @@ public class Wallpaper extends Activity { } protected void formatIntent(Intent intent) { + // TODO: A temporary file is NOT necessary + // The CropImage intent should be able to set the wallpaper directly + // without writing to a file, which we then need to read here to write + // it again as the final wallpaper, this is silly File f = getFileStreamPath("temp-wallpaper"); (new File(f.getParent())).mkdirs(); mTempFilePath = f.toString(); @@ -162,6 +169,10 @@ public class Wallpaper extends Activity { intent.putExtra("scale", true); intent.putExtra("noFaceDetection", true); intent.putExtra("output", Uri.parse("file:/" + mTempFilePath)); + intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.name()); + // TODO: we should have an extra called "setWallpaper" to ask CropImage to + // set the cropped image as a wallpaper directly + // This means the SetWallpaperThread should be moved out of this class to CropImage } @Override @@ -169,7 +180,8 @@ public class Wallpaper extends Activity { if ((requestCode == PHOTO_PICKED || requestCode == CROP_DONE) && (resultCode == RESULT_OK) && (data != null)) { try { - InputStream s = new FileInputStream(mTempFilePath); + File tempFile = new File(mTempFilePath); + InputStream s = new FileInputStream(tempFile); Bitmap bitmap = BitmapFactory.decodeStream(s); if (bitmap == null) { Log.e(LOG_TAG, "Failed to set wallpaper. Couldn't get bitmap for path " + mTempFilePath); @@ -177,7 +189,7 @@ public class Wallpaper extends Activity { if (android.util.Config.LOGV) Log.v(LOG_TAG, "bitmap size is " + bitmap.getWidth() + " / " + bitmap.getHeight()); mHandler.sendEmptyMessage(SHOW_PROGRESS); - new SetWallpaperThread(bitmap, mHandler, this).start(); + new SetWallpaperThread(bitmap, mHandler, this, tempFile).start(); } mDoLaunch = false; } catch (FileNotFoundException ex) { |