From 37f3611bbdc787c996a99894dcef7d8fb77e3f7c Mon Sep 17 00:00:00 2001 From: Sascha Haeberling Date: Tue, 6 Aug 2013 14:31:52 -0700 Subject: This adds the following four CLs to Camera2: http://ag/339319 http://ag/338974 http://ag/338401 http://ag/324149 Adapted to work in Camera2 and adding a .gitignore so that temporary Eclipse files and folders are not submitted. Change-Id: I40295c7f0139f76270c44f0ca395c0574a288569 --- .gitignore | 6 + res/layout-land/camera_controls.xml | 3 +- res/layout-port/camera_controls.xml | 3 +- res/layout/camera_filmstrip.xml | 39 ++++- res/layout/video_module.xml | 7 + src/com/android/camera/AnimationManager.java | 157 +++++++++++++++++++ src/com/android/camera/CameraActivity.java | 162 ++++++++++++++----- src/com/android/camera/ImageTaskManager.java | 2 +- src/com/android/camera/PhotoModule.java | 55 ++----- src/com/android/camera/PhotoUI.java | 65 ++++---- src/com/android/camera/Util.java | 21 ++- src/com/android/camera/VideoModule.java | 44 +++--- src/com/android/camera/VideoUI.java | 38 +++-- .../data/AbstractLocalDataAdapterWrapper.java | 10 ++ src/com/android/camera/data/CameraDataAdapter.java | 174 ++++++++++++--------- .../android/camera/data/FixedFirstDataAdapter.java | 45 ++++-- .../android/camera/data/FixedLastDataAdapter.java | 57 +++++-- src/com/android/camera/data/LocalData.java | 128 +++++++++++++-- src/com/android/camera/data/LocalDataAdapter.java | 27 ++++ src/com/android/camera/ui/FilmStripView.java | 40 ++--- 20 files changed, 794 insertions(+), 289 deletions(-) create mode 100644 .gitignore create mode 100644 src/com/android/camera/AnimationManager.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..871a664ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Eclipse project files and paths. +.classpath +.project +.settings/ +bin/ +libs/ diff --git a/res/layout-land/camera_controls.xml b/res/layout-land/camera_controls.xml index 432ae9ebd..d1772401e 100644 --- a/res/layout-land/camera_controls.xml +++ b/res/layout-land/camera_controls.xml @@ -59,11 +59,12 @@ android:scaleType="center" android:src="@drawable/btn_new_shutter" /> - diff --git a/res/layout-port/camera_controls.xml b/res/layout-port/camera_controls.xml index 7dd66e44f..5f89830c5 100644 --- a/res/layout-port/camera_controls.xml +++ b/res/layout-port/camera_controls.xml @@ -59,11 +59,12 @@ android:scaleType="center" android:src="@drawable/btn_new_shutter" /> - \ No newline at end of file diff --git a/res/layout/camera_filmstrip.xml b/res/layout/camera_filmstrip.xml index 935f38ae8..d94a9d2a8 100644 --- a/res/layout/camera_filmstrip.xml +++ b/res/layout/camera_filmstrip.xml @@ -38,4 +38,41 @@ android:visibility="gone" android:src="@drawable/ic_view_photosphere" /> - \ No newline at end of file + + + + + + + + diff --git a/res/layout/video_module.xml b/res/layout/video_module.xml index 9eb3e84e2..2df1adc9b 100644 --- a/res/layout/video_module.xml +++ b/res/layout/video_module.xml @@ -21,6 +21,13 @@ android:id="@+id/preview_content" android:layout_width="match_parent" android:layout_height="match_parent" /> + scaleY ? scaleX : scaleY; + + int centerX = view.getLeft() + view.getWidth() / 2; + int centerY = view.getTop() + view.getHeight() / 2; + + ObjectAnimator slide = ObjectAnimator.ofFloat(view, "translationX", 0f, slideDistance) + .setDuration(AnimationManager.SLIDE_DURATION); + slide.setStartDelay(AnimationManager.SHRINK_DURATION + AnimationManager.HOLD_DURATION); + mCaptureAnimator = new AnimatorSet(); + mCaptureAnimator.playTogether( + ObjectAnimator.ofFloat(view, "scaleX", scale, 1f) + .setDuration(AnimationManager.SHRINK_DURATION), + ObjectAnimator.ofFloat(view, "scaleY", scale, 1f) + .setDuration(AnimationManager.SHRINK_DURATION), + ObjectAnimator.ofFloat(view, "translationX", + parentView.getWidth() / 2 - centerX, 0f) + .setDuration(AnimationManager.SHRINK_DURATION), + ObjectAnimator.ofFloat(view, "translationY", + parentView.getHeight() / 2 - centerY, 0f) + .setDuration(AnimationManager.SHRINK_DURATION), + slide); + mCaptureAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + view.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animator) { + view.setScaleX(1f); + view.setScaleX(1f); + view.setTranslationX(0f); + view.setTranslationY(0f); + view.setVisibility(View.INVISIBLE); + mCaptureAnimator.removeAllListeners(); + mCaptureAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animator) { + view.setVisibility(View.INVISIBLE); + } + + @Override + public void onAnimationRepeat(Animator animator) { + // Do nothing. + } + }); + mCaptureAnimator.start(); + } + + /** + * Starts flash animation. + * @params flashOverlay the overlay that will animate on alpha to make the flash impression + */ + public void startFlashAnimation(final View flashOverlay) { + // End the previous animation if the previous one is still running + if (mFlashAnim != null && mFlashAnim.isRunning()) { + mFlashAnim.cancel(); + } + // Start new flash animation. + mFlashAnim = ObjectAnimator.ofFloat(flashOverlay, "alpha", + AnimationManager.FLASH_ALPHA_START, AnimationManager.FLASH_ALPHA_END); + mFlashAnim.setDuration(AnimationManager.FLASH_DURATION); + mFlashAnim.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + flashOverlay.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animator) { + flashOverlay.setAlpha(0f); + flashOverlay.setVisibility(View.GONE); + mFlashAnim.removeAllListeners(); + mFlashAnim = null; + } + + @Override + public void onAnimationCancel(Animator animator) { + // Do nothing. + } + + @Override + public void onAnimationRepeat(Animator animator) { + // Do nothing. + } + }); + mFlashAnim.start(); + } + + /** + * Cancels on-going flash animation and capture animation, if any. + */ + public void cancelAnimations() { + // End the previous animation if the previous one is still running + if (mFlashAnim != null && mFlashAnim.isRunning()) { + mFlashAnim.cancel(); + } + if (mCaptureAnimator != null && mCaptureAnimator.isStarted()) { + mCaptureAnimator.cancel(); + } + } +} \ No newline at end of file diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index e110327d8..57da90e60 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -32,6 +32,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.provider.Settings; +import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.OrientationEventListener; @@ -40,6 +41,7 @@ import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; +import android.widget.ProgressBar; import com.android.camera.data.CameraDataAdapter; import com.android.camera.data.CameraPreviewData; @@ -51,8 +53,8 @@ import com.android.camera.support.common.ApiHelper; import com.android.camera.ui.CameraSwitcher; import com.android.camera.ui.CameraSwitcher.CameraSwitchListener; import com.android.camera.ui.FilmStripView; -import com.android.camera.util.PhotoSphereHelper; import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; +import com.android.camera.util.PhotoSphereHelper; import com.android.camera.util.RefocusHelper; import com.android.camera2.R; @@ -82,6 +84,8 @@ public class CameraActivity extends Activity private CameraModule mCurrentModule; private View mRootView; private FilmStripView mFilmStripView; + private ProgressBar mBottomProgress; + private View mPanoStitchingPanel; private int mResultCodeForTesting; private Intent mResultDataForTesting; private OnScreenHint mStorageHint; @@ -150,26 +154,65 @@ public class CameraActivity extends Activity sFirstStartAfterScreenOn = false; } - private FilmStripView.Listener mFilmStripListener = new FilmStripView.Listener() { - @Override - public void onDataPromoted(int dataID) { - removeData(dataID); - } + private FilmStripView.Listener mFilmStripListener = + new FilmStripView.Listener() { + @Override + public void onDataPromoted(int dataID) { + removeData(dataID); + } - @Override - public void onDataDemoted(int dataID) { - removeData(dataID); - } + @Override + public void onDataDemoted(int dataID) { + removeData(dataID); + } - @Override - public void onDataFullScreenChange(int dataID, boolean full) { - } + @Override + public void onDataFullScreenChange(int dataID, boolean full) { + } - @Override - public void onSwitchMode(boolean toCamera) { - mCurrentModule.onSwitchMode(toCamera); - } - }; + @Override + public void onSwitchMode(boolean toCamera) { + mCurrentModule.onSwitchMode(toCamera); + } + + @Override + public void onCurrentDataChanged(int dataID, boolean current) { + if (!current) { + hidePanoStitchingProgress(); + } else { + LocalData currentData = mDataAdapter.getLocalData(dataID); + if (currentData == null) { + Log.w(TAG, "Current data ID not found."); + hidePanoStitchingProgress(); + return; + } + Uri contentUri = currentData.getContentUri(); + if (contentUri == null) { + hidePanoStitchingProgress(); + return; + } + int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri); + if (panoStitchingProgress < 0) { + hidePanoStitchingProgress(); + return; + } + showPanoStitchingProgress(); + updateStitchingProgress(panoStitchingProgress); + } + } + }; + + private void hidePanoStitchingProgress() { + mPanoStitchingPanel.setVisibility(View.GONE); + } + + private void showPanoStitchingProgress() { + mPanoStitchingPanel.setVisibility(View.VISIBLE); + } + + private void updateStitchingProgress(int progress) { + mBottomProgress.setProgress(progress); + } private Runnable mDeletionRunnable = new Runnable() { @Override @@ -181,16 +224,50 @@ public class CameraActivity extends Activity private ImageTaskManager.TaskListener mStitchingListener = new ImageTaskManager.TaskListener() { @Override - public void onTaskQueued(String filePath, Uri imageUri) { + public void onTaskQueued(String filePath, final Uri imageUri) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + notifyNewMedia(imageUri); + } + }); } @Override - public void onTaskDone(String filePath, Uri imageUri) { + public void onTaskDone(String filePath, final Uri imageUri) { + Log.v(TAG, "onTaskDone:" + filePath); + mMainHandler.post(new Runnable() { + @Override + public void run() { + int doneID = mDataAdapter.findDataByContentUri(imageUri); + int currentDataId = mFilmStripView.getCurrentId(); + + if (currentDataId == doneID) { + hidePanoStitchingProgress(); + updateStitchingProgress(0); + } + + mDataAdapter.refresh(getContentResolver(), imageUri); + } + }); } @Override public void onTaskProgress( - String filePath, Uri imageUri, int progress) { + String filePath, final Uri imageUri, final int progress) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + int currentDataId = mFilmStripView.getCurrentId(); + if (currentDataId == -1) { + return; + } + if (imageUri.equals( + mDataAdapter.getLocalData(currentDataId).getContentUri())) { + updateStitchingProgress(progress); + } + } + }); } }; @@ -207,6 +284,8 @@ public class CameraActivity extends Activity } else if (mimeType.startsWith("image/")) { Util.broadcastNewPicture(this, uri); mDataAdapter.addNewPhoto(cr, uri); + } else if (mimeType.startsWith("application/stitching-preview")) { + mDataAdapter.addNewPhoto(cr, uri); } else { android.util.Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri); @@ -276,9 +355,12 @@ public class CameraActivity extends Activity LayoutInflater inflater = getLayoutInflater(); View rootLayout = inflater.inflate(R.layout.camera, null, false); mRootView = rootLayout.findViewById(R.id.camera_app_root); + mPanoStitchingPanel = (View) findViewById(R.id.pano_stitching_progress_panel); + mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar); mCameraPreviewData = new CameraPreviewData(rootLayout, FilmStripView.ImageData.SIZE_FULL, FilmStripView.ImageData.SIZE_FULL); + // Put a CameraPreviewData at the first position. mWrappedDataAdapter = new FixedFirstDataAdapter( new CameraDataAdapter(new ColorDrawable( getResources().getColor(R.color.photo_placeholder))), @@ -296,6 +378,25 @@ public class CameraActivity extends Activity mOrientationListener = new MyOrientationEventListener(this); mMainHandler = new Handler(getMainLooper()); bindMediaSaveService(); + + if (!mSecureCamera) { + mDataAdapter = mWrappedDataAdapter; + mDataAdapter.requestLoad(getContentResolver()); + } else { + // Put a lock placeholder as the last image by setting its date to 0. + ImageView v = (ImageView) getLayoutInflater().inflate( + R.layout.secure_album_placeholder, null); + mDataAdapter = new FixedLastDataAdapter( + mWrappedDataAdapter, + new LocalData.LocalViewData( + v, + v.getDrawable().getIntrinsicWidth(), + v.getDrawable().getIntrinsicHeight(), + 0, 0)); + // Flush out all the original data. + mDataAdapter.flush(); + } + mFilmStripView.setDataAdapter(mDataAdapter); } private void setRotationAnimation() { @@ -343,25 +444,6 @@ public class CameraActivity extends Activity public void onStart() { super.onStart(); - // The loading is done in background and will update the filmstrip later. - if (!mSecureCamera) { - mDataAdapter = mWrappedDataAdapter; - mDataAdapter.requestLoad(getContentResolver()); - mFilmStripView.setDataAdapter(mDataAdapter); - } else { - // Put a lock placeholder as the last image by setting its date to 0. - ImageView v = (ImageView) getLayoutInflater().inflate( - R.layout.secure_album_placeholder, null); - mDataAdapter = new FixedLastDataAdapter( - mWrappedDataAdapter, - new LocalData.LocalViewData( - v, - v.getDrawable().getIntrinsicWidth(), - v.getDrawable().getIntrinsicHeight(), - 0, 0)); - // Flush out all the original data. - mDataAdapter.flush(); - } mPanoramaViewHelper.onStart(); } diff --git a/src/com/android/camera/ImageTaskManager.java b/src/com/android/camera/ImageTaskManager.java index 1324942fd..601de4c50 100644 --- a/src/com/android/camera/ImageTaskManager.java +++ b/src/com/android/camera/ImageTaskManager.java @@ -21,7 +21,7 @@ import android.net.Uri; /** * The interface for background image processing task manager. */ -interface ImageTaskManager { +public interface ImageTaskManager { /** * Callback interface for task events. diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 89833b3b5..a61c2676e 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -65,13 +65,13 @@ import com.android.camera.CameraManager.CameraAFMoveCallback; import com.android.camera.CameraManager.CameraPictureCallback; import com.android.camera.CameraManager.CameraProxy; import com.android.camera.CameraManager.CameraShutterCallback; -import com.android.camera.support.common.ApiHelper; import com.android.camera.support.filtershow.crop.CropExtras; import com.android.camera.ui.CountDownView.OnCountDownFinishedListener; import com.android.camera.ui.PopupManager; import com.android.camera.ui.RotateTextToast; import com.android.camera.util.UsageStatistics; import com.android.camera2.R; +import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; import com.android.gallery3d.exif.Rational; @@ -103,7 +103,6 @@ public class PhotoModule private static final int START_PREVIEW_DONE = 10; private static final int OPEN_CAMERA_FAIL = 11; private static final int CAMERA_DISABLED = 12; - private static final int CAPTURE_ANIMATION_DONE = 13; // The subset of parameters we need to update in setCameraParameters(). private static final int UPDATE_PARAM_INITIALIZE = 1; @@ -170,13 +169,6 @@ public class PhotoModule } }; - private Runnable mFlashRunnable = new Runnable() { - @Override - public void run() { - animateFlash(); - } - }; - private final StringBuilder mBuilder = new StringBuilder(); private final Formatter mFormatter = new Formatter(mBuilder); private final Object[] mFormatterArgs = new Object[1]; @@ -394,10 +386,6 @@ public class PhotoModule R.string.camera_disabled); break; } - case CAPTURE_ANIMATION_DONE: { - mUI.enablePreviewThumb(false); - break; - } } } } @@ -684,10 +672,10 @@ public class PhotoModule private final class ShutterCallback implements CameraShutterCallback { - private boolean mAnimateFlash; + private boolean mNeedsAnimation; - public ShutterCallback(boolean animateFlash) { - mAnimateFlash = animateFlash; + public ShutterCallback(boolean needsAnimation) { + mNeedsAnimation = needsAnimation; } @Override @@ -695,8 +683,13 @@ public class PhotoModule mShutterCallbackTime = System.currentTimeMillis(); mShutterLag = mShutterCallbackTime - mCaptureStartTime; Log.v(TAG, "mShutterLag = " + mShutterLag + "ms"); - if (mAnimateFlash) { - mActivity.runOnUiThread(mFlashRunnable); + if (mNeedsAnimation) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + animateAfterShutter(); + } + }); } } } @@ -735,9 +728,11 @@ public class PhotoModule if (mPaused) { return; } - //TODO: We should show the picture taken rather than frozen preview here if (mIsImageCaptureIntent) { stopPreview(); + } else { + // Animate capture with real jpeg data instead of a preview frame. + mUI.animateCapture(jpegData); } if (mSceneMode == Util.SCENE_MODE_HDR) { mUI.showSwitcher(); @@ -762,18 +757,6 @@ public class PhotoModule Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = " + mPictureDisplayedToJpegCallbackTime + "ms"); - /*TODO: - // Only animate when in full screen capture mode - // i.e. If monkey/a user swipes to the gallery during picture taking, - // don't show animation - if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent - && mActivity.mShowCameraAppView) { - // Finish capture animation - mHandler.removeMessages(CAPTURE_ANIMATION_DONE); - ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide(); - mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - CaptureAnimManager.getAnimationDuration()); - } */ mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden. if (!mIsImageCaptureIntent) { if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) { @@ -919,16 +902,12 @@ public class PhotoModule } } - private void animateFlash() { + private void animateAfterShutter() { // Only animate when in full screen capture mode // i.e. If monkey/a user swipes to the gallery during picture taking, // don't show animation if (!mIsImageCaptureIntent) { mUI.animateFlash(); - - // TODO: mUI.enablePreviewThumb(true); - // mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - // CaptureAnimManager.getAnimationDuration()); } } @@ -948,7 +927,7 @@ public class PhotoModule final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR); if (animateBefore) { - animateFlash(); + animateAfterShutter(); } // Set rotation and gps data. @@ -1130,7 +1109,7 @@ public class PhotoModule newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true); } - // TODO ... + // TODO: Share this constant. final String CROP_ACTION = "com.android.camera.action.CROP"; Intent cropIntent = new Intent(CROP_ACTION); diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index e10570a08..a0e8b9b82 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -19,16 +19,15 @@ package com.android.camera; import java.util.List; -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.app.AlertDialog; import android.content.DialogInterface; +import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.Camera.Face; import android.hardware.Camera.Size; +import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.util.Log; @@ -41,6 +40,7 @@ import android.view.ViewGroup; import android.view.ViewStub; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; +import android.widget.ImageView; import android.widget.Toast; import com.android.camera.CameraPreference.OnPreferenceChangedListener; @@ -69,6 +69,8 @@ public class PhotoUI implements PieListener, private static final String TAG = "CAM_UI"; private static final int UPDATE_TRANSFORM_MATRIX = 1; + private static final int DOWN_SAMPLE_FACTOR = 4; + private final AnimationManager mAnimationManager; private CameraActivity mActivity; private PhotoController mController; private PreviewGestures mGestures; @@ -108,8 +110,7 @@ public class PhotoUI implements PieListener, private float mSurfaceTextureUncroppedWidth; private float mSurfaceTextureUncroppedHeight; - private View mPreviewThumb; - private ObjectAnimator mFlashAnim; + private ImageView mPreviewThumb; private View mFlashOverlay; private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener; @@ -156,25 +157,25 @@ public class PhotoUI implements PieListener, } }; - private ValueAnimator.AnimatorListener mAnimatorListener = - new ValueAnimator.AnimatorListener() { + private class DecodeTask extends AsyncTask { + private final byte [] mData; - @Override - public void onAnimationCancel(Animator arg0) {} - - @Override - public void onAnimationEnd(Animator arg0) { - mFlashOverlay.setAlpha(0f); - mFlashOverlay.setVisibility(View.GONE); - mFlashAnim.removeListener(this); + public DecodeTask(byte[] data) { + mData = data; } @Override - public void onAnimationRepeat(Animator arg0) {} + protected Bitmap doInBackground(Integer... params) { + // Decode image in background. + return Util.downSample(mData, DOWN_SAMPLE_FACTOR); + } @Override - public void onAnimationStart(Animator arg0) {} - }; + protected void onPostExecute(Bitmap bitmap) { + mPreviewThumb.setImageBitmap(bitmap); + mAnimationManager.startCaptureAnimation(mPreviewThumb); + } + } public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { mActivity = activity; @@ -208,6 +209,7 @@ public class PhotoUI implements PieListener, } mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls); ((CameraRootView) mRootView).setDisplayChangeListener(this); + mAnimationManager = new AnimationManager(); } public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) { @@ -328,6 +330,12 @@ public class PhotoUI implements PieListener, updateOnScreenIndicators(params, prefGroup, prefs); } + public void animateCapture(final byte[] jpegData) { + // Decode jpeg byte array and then animate the jpeg + DecodeTask task = new DecodeTask(jpegData); + task.execute(); + } + private void openMenu() { if (mPieRenderer != null) { // If autofocus is not finished, cancel autofocus so that the @@ -341,7 +349,7 @@ public class PhotoUI implements PieListener, public void initializeControlByIntent() { mBlocker = mRootView.findViewById(R.id.blocker); - mPreviewThumb = mRootView.findViewById(R.id.preview_thumb); + mPreviewThumb = (ImageView) mRootView.findViewById(R.id.preview_thumb); mPreviewThumb.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -498,16 +506,7 @@ public class PhotoUI implements PieListener, } public void animateFlash() { - // End the previous animation if the previous one is still running - if (mFlashAnim != null && mFlashAnim.isRunning()) { - mFlashAnim.end(); - } - // Start new flash animation. - mFlashOverlay.setVisibility(View.VISIBLE); - mFlashAnim = ObjectAnimator.ofFloat((Object) mFlashOverlay, "alpha", 0.3f, 0f); - mFlashAnim.setDuration(300); - mFlashAnim.addListener(mAnimatorListener); - mFlashAnim.start(); + mAnimationManager.startFlashAnimation(mFlashOverlay); } public void enableGestures(boolean enable) { @@ -570,14 +569,6 @@ public class PhotoUI implements PieListener, if (!toCamera && mCountDownView != null) mCountDownView.cancelCountDown(); } - public void enablePreviewThumb(boolean enabled) { - if (enabled) { - mPreviewThumb.setVisibility(View.VISIBLE); - } else { - mPreviewThumb.setVisibility(View.GONE); - } - } - public boolean removeTopLevelPopup() { // Remove the top level popup or dialog box and return true if there's any if (mPopup != null) { diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java index 43ad22e17..11176a79b 100644 --- a/src/com/android/camera/Util.java +++ b/src/com/android/camera/Util.java @@ -24,7 +24,6 @@ import java.util.Date; import java.util.List; import java.util.StringTokenizer; -import com.android.camera2.R; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; @@ -62,7 +61,8 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.Toast; -import com.android.camera.support.common.ApiHelper; +import com.android.camera2.R; +import com.android.gallery3d.common.ApiHelper; /** * Collection of utility functions used in this package. @@ -89,8 +89,8 @@ public class Util { public static final String TRUE = "true"; public static final String FALSE = "false"; - /** Has to by in sync with the receiving MovieActivity. */ - public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back"; + /** Has to be in sync with the receiving MovieActivity. */ + public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back"; public static boolean isSupported(String value, List supported) { return supported == null ? false : supported.indexOf(value) >= 0; @@ -711,6 +711,19 @@ public class Util { return rotation; } + /** + * Down-samples a jpeg byte array. + * @param data a byte array of jpeg data + * @param downSampleFactor down-sample factor + * @return decoded and down-sampled bitmap + */ + public static Bitmap downSample(final byte[] data, int downSampleFactor) { + final BitmapFactory.Options opts = new BitmapFactory.Options(); + // Downsample the image + opts.inSampleSize = downSampleFactor; + return BitmapFactory.decodeByteArray(data, 0, data.length, opts); + } + public static void setGpsParameters(Parameters parameters, Location loc) { // Clear previous GPS location from the parameters. parameters.removeGpsData(); diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index aa085af0e..1516cb5d0 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -92,8 +92,6 @@ public class VideoModule implements CameraModule, private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7; private static final int SWITCH_CAMERA = 8; private static final int SWITCH_CAMERA_START_ANIMATION = 9; - private static final int HIDE_SURFACE_VIEW = 10; - private static final int CAPTURE_ANIMATION_DONE = 11; private static final int SCREEN_DELAY = 2 * 60 * 1000; @@ -107,7 +105,6 @@ public class VideoModule implements CameraModule, private static final String EXTRA_QUICK_CAPTURE = "android.intent.extra.quickCapture"; - private static final int MIN_THUMB_SIZE = 64; // module fields private CameraActivity mActivity; private boolean mPaused; @@ -200,9 +197,7 @@ public class VideoModule implements CameraModule, @Override public void onMediaSaved(Uri uri) { if (uri != null) { - mActivity.sendBroadcast( - new Intent(Util.ACTION_NEW_VIDEO, uri)); - Util.broadcastNewPicture(mActivity, uri); + mActivity.notifyNewMedia(uri); } } }; @@ -212,7 +207,7 @@ public class VideoModule implements CameraModule, @Override public void onMediaSaved(Uri uri) { if (uri != null) { - Util.broadcastNewPicture(mActivity, uri); + mActivity.notifyNewMedia(uri); } } }; @@ -295,11 +290,6 @@ public class VideoModule implements CameraModule, break; } - case CAPTURE_ANIMATION_DONE: { - mUI.enablePreviewThumb(false); - break; - } - default: Log.v(TAG, "Unhandled message: " + msg.what); break; @@ -555,16 +545,12 @@ public class VideoModule implements CameraModule, // back to use SurfaceTexture for preview and we need to stop then start // the preview. This will cause the preview flicker since the preview // will not be continuous for a short period of time. - // TODO: need to get the capture animation to work - // ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); - - mUI.enablePreviewThumb(true); - // Make sure to disable the thumbnail preview after the - // animation is done to disable the click target. - mHandler.removeMessages(CAPTURE_ANIMATION_DONE); - mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - CaptureAnimManager.getAnimationDuration()); + mUI.animateFlash(); + Bitmap bitmap = getVideoThumbnail(); + if (bitmap != null) { + mUI.animateCapture(bitmap); + } } } } @@ -1425,7 +1411,7 @@ public class VideoModule implements CameraModule, private void startVideoRecording() { Log.v(TAG, "startVideoRecording"); - mUI.enablePreviewThumb(false); + mUI.cancelAnimations(); mUI.setSwipingEnabled(false); mActivity.updateStorageSpaceAndHint(); @@ -1502,8 +1488,7 @@ public class VideoModule implements CameraModule, UsageStatistics.ACTION_CAPTURE_START, "Video"); } - private void showCaptureResult() { - mIsInReviewMode = true; + private Bitmap getVideoThumbnail() { Bitmap bitmap = null; if (mVideoFileDescriptor != null) { bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(), @@ -1518,9 +1503,16 @@ public class VideoModule implements CameraModule, CameraInfo[] info = CameraHolder.instance().getCameraInfo(); boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT); bitmap = Util.rotateAndMirror(bitmap, 0, mirror); - mUI.showReviewImage(bitmap); } + return bitmap; + } + private void showCaptureResult() { + mIsInReviewMode = true; + Bitmap bitmap = getVideoThumbnail(); + if (bitmap != null) { + mUI.showReviewImage(bitmap); + } mUI.showReviewControls(); mUI.enableCameraControls(false); mUI.showTimeLapseUI(false); @@ -2086,7 +2078,7 @@ public class VideoModule implements CameraModule, if (mParameters == null) return; if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { if (enabled) { - // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); + mUI.animateFlash(); } else { mUI.showPreviewBorder(enabled); } diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java index 06c9795f3..1f6505fcf 100644 --- a/src/com/android/camera/VideoUI.java +++ b/src/com/android/camera/VideoUI.java @@ -91,6 +91,7 @@ public class VideoUI implements PieRenderer.PieListener, private int mZoomMax; private List mZoomRatios; private View mPreviewThumb; + private View mFlashOverlay; private SurfaceView mSurfaceView = null; private int mPreviewWidth = 0; @@ -99,6 +100,7 @@ public class VideoUI implements PieRenderer.PieListener, private float mSurfaceTextureUncroppedHeight; private float mAspectRatio = 4f / 3f; private Matrix mMatrix = null; + private final AnimationManager mAnimationManager; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -141,6 +143,7 @@ public class VideoUI implements PieRenderer.PieListener, mTextureView.setSurfaceTextureListener(this); mRootView.addOnLayoutChangeListener(mLayoutListener); ((CameraRootView) mRootView).setDisplayChangeListener(this); + mFlashOverlay = mRootView.findViewById(R.id.flash_overlay); mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button); mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher); mSwitcher.setCurrentIndex(CameraSwitcher.VIDEO_MODULE_INDEX); @@ -148,6 +151,7 @@ public class VideoUI implements PieRenderer.PieListener, initializeMiscControls(); initializeControlByIntent(); initializeOverlay(); + mAnimationManager = new AnimationManager(); } @@ -266,6 +270,29 @@ public class VideoUI implements PieRenderer.PieListener, } } + /** + * Starts a flash animation + */ + public void animateFlash() { + mAnimationManager.startFlashAnimation(mFlashOverlay); + } + + /** + * Starts a capture animation + * @param bitmap the captured image that we shrink and slide in the animation + */ + public void animateCapture(Bitmap bitmap) { + ((ImageView) mPreviewThumb).setImageBitmap(bitmap); + mAnimationManager.startCaptureAnimation(mPreviewThumb); + } + + /** + * Cancels on-going animations + */ + public void cancelAnimations() { + mAnimationManager.cancelAnimations(); + } + public void hideUI() { mCameraControls.setVisibility(View.INVISIBLE); mSwitcher.closePopup(); @@ -623,17 +650,6 @@ public class VideoUI implements PieRenderer.PieListener, mController.updateCameraOrientation(); } - /** - * Enable or disable the preview thumbnail for click events. - */ - public void enablePreviewThumb(boolean enabled) { - if (enabled) { - mPreviewThumb.setVisibility(View.VISIBLE); - } else { - mPreviewThumb.setVisibility(View.GONE); - } - } - private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { @Override public void onZoomValueChanged(int index) { diff --git a/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java b/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java index 66c55850a..5df87f5a8 100644 --- a/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java +++ b/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java @@ -73,6 +73,11 @@ public abstract class AbstractLocalDataAdapterWrapper implements LocalDataAdapte mAdapter.addNewPhoto(resolver, uri); } + @Override + public void insertData(LocalData data) { + mAdapter.insertData(data); + } + @Override public void flush() { mAdapter.flush(); @@ -87,4 +92,9 @@ public abstract class AbstractLocalDataAdapterWrapper implements LocalDataAdapte public boolean undoDataRemoval() { return mAdapter.undoDataRemoval(); } + + @Override + public void refresh(ContentResolver resolver, Uri uri) { + mAdapter.refresh(resolver, uri); + } } diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java index e384b1512..aaf5ebe59 100644 --- a/src/com/android/camera/data/CameraDataAdapter.java +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -16,11 +16,6 @@ package com.android.camera.data; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; @@ -34,11 +29,16 @@ import android.view.View; import com.android.camera.Storage; import com.android.camera.ui.FilmStripView.ImageData; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + /** * A {@link LocalDataAdapter} that provides data in the camera folder. */ public class CameraDataAdapter implements LocalDataAdapter { - private static final String TAG = CameraDataAdapter.class.getSimpleName(); + private static final String TAG = "CAM_CameraDataAdapter"; private static final int DEFAULT_DECODE_SIZE = 3000; private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" }; @@ -63,6 +63,15 @@ public class CameraDataAdapter implements LocalDataAdapter { qtask.execute(resolver); } + @Override + public LocalData getLocalData(int dataID) { + if (mImages == null || dataID < 0 || dataID >= mImages.size()) { + return null; + } + + return mImages.get(dataID); + } + @Override public int getTotalNumber() { if (mImages == null) { @@ -73,7 +82,7 @@ public class CameraDataAdapter implements LocalDataAdapter { @Override public ImageData getImageData(int id) { - return getData(id); + return getLocalData(id); } @Override @@ -115,11 +124,6 @@ public class CameraDataAdapter implements LocalDataAdapter { } } - @Override - public void onDataCentered(int dataID, boolean centered) { - // do nothing. - } - @Override public boolean canSwipeInFullScreen(int dataID) { if (dataID < mImages.size() && dataID > 0) { @@ -138,49 +142,60 @@ public class CameraDataAdapter implements LocalDataAdapter { mListener.onDataRemoved(dataID, d); } - private void insertData(LocalData data) { - if (mImages == null) { - mImages = new ArrayList(); - } - - // Since this function is mostly for adding the newest data, - // a simple linear search should yield the best performance over a - // binary search. - int pos = 0; - Comparator comp = new LocalData.NewestFirstComparator(); - for (; pos < mImages.size() - && comp.compare(data, mImages.get(pos)) > 0; pos++); - mImages.add(pos, data); - if (mListener != null) { - mListener.onDataInserted(pos, data); - } - } - + // TODO: put the database query on background thread @Override public void addNewVideo(ContentResolver cr, Uri uri) { Cursor c = cr.query(uri, LocalData.Video.QUERY_PROJECTION, MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, LocalData.Video.QUERY_ORDER); - if (c != null && c.moveToFirst()) { - insertData(LocalData.Video.buildFromCursor(c)); + if (c == null || !c.moveToFirst()) { + return; + } + int pos = findDataByContentUri(uri); + LocalData.Video newData = LocalData.Video.buildFromCursor(c); + if (pos != -1) { + // A duplicate one, just do a substitute. + updateData(pos, newData); + } else { + // A new data. + insertData(newData); } } + // TODO: put the database query on background thread @Override public void addNewPhoto(ContentResolver cr, Uri uri) { Cursor c = cr.query(uri, LocalData.Photo.QUERY_PROJECTION, MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, LocalData.Photo.QUERY_ORDER); - if (c != null && c.moveToFirst()) { - insertData(LocalData.Photo.buildFromCursor(c)); + if (c == null || !c.moveToFirst()) { + return; + } + int pos = findDataByContentUri(uri); + LocalData.Photo newData = LocalData.Photo.buildFromCursor(c); + if (pos != -1) { + // a duplicate one, just do a substitute. + Log.v(TAG, "found duplicate photo"); + updateData(pos, newData); + } else { + // a new data. + insertData(newData); } } @Override public int findDataByContentUri(Uri uri) { - // TODO: find the data. + for (int i = 0; i < mImages.size(); i++) { + Uri u = mImages.get(i).getContentUri(); + if (u == null) { + continue; + } + if (u.equals(uri)) { + return i; + } + } return -1; } @@ -208,51 +223,62 @@ public class CameraDataAdapter implements LocalDataAdapter { replaceData(null); } - private LocalData getData(int id) { - if (mImages == null || id >= mImages.size() || id < 0) { - return null; + @Override + public void refresh(ContentResolver resolver, Uri contentUri) { + int pos = findDataByContentUri(contentUri); + if (pos == -1) { + return; + } + + LocalData data = mImages.get(pos); + if (data.refresh(resolver)) { + updateData(pos, data); } - return mImages.get(id); } - // Update all the data but keep the camera data if already set. - private void replaceData(List list) { - boolean changed = (list != mImages); - LocalData cameraData = null; - if (mImages != null && mImages.size() > 0) { - cameraData = mImages.get(0); - if (cameraData.getType() != ImageData.TYPE_CAMERA_PREVIEW) { - cameraData = null; - } + @Override + public void updateData(final int pos, LocalData data) { + mImages.set(pos, data); + if (mListener != null) { + mListener.onDataUpdated(new UpdateReporter() { + @Override + public boolean isDataRemoved(int dataID) { + return false; + } + + @Override + public boolean isDataUpdated(int dataID) { + return (dataID == pos); + } + }); } + } - mImages = list; - if (cameraData != null) { - // camera view exists, so we make sure at least 1 data is in the list. - if (mImages == null) { - mImages = new ArrayList(); - } - mImages.add(0, cameraData); - if (mListener != null) { - // Only the camera data is not changed, everything else is changed. - mListener.onDataUpdated(new UpdateReporter() { - @Override - public boolean isDataRemoved(int id) { - return false; - } + @Override + public void insertData(LocalData data) { + if (mImages == null) { + mImages = new ArrayList(); + } - @Override - public boolean isDataUpdated(int id) { - if (id == 0) return false; - return true; - } - }); - } - } else { - // both might be null. - if (changed) { - mListener.onDataLoaded(); - } + // Since this function is mostly for adding the newest data, + // a simple linear search should yield the best performance over a + // binary search. + int pos = 0; + Comparator comp = new LocalData.NewestFirstComparator(); + for (; pos < mImages.size() + && comp.compare(data, mImages.get(pos)) > 0; pos++); + mImages.add(pos, data); + if (mListener != null) { + mListener.onDataInserted(pos, data); + } + } + + /** Update all the data */ + private void replaceData(List list) { + boolean changed = (list != mImages); + mImages = list; + if (changed) { + mListener.onDataLoaded(); } } diff --git a/src/com/android/camera/data/FixedFirstDataAdapter.java b/src/com/android/camera/data/FixedFirstDataAdapter.java index 34ba0a1a0..2bff22aa4 100644 --- a/src/com/android/camera/data/FixedFirstDataAdapter.java +++ b/src/com/android/camera/data/FixedFirstDataAdapter.java @@ -32,7 +32,7 @@ import com.android.camera.ui.FilmStripView.ImageData; public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper implements DataAdapter.Listener { - private final LocalData mFirstData; + private LocalData mFirstData; private Listener mListener; /** @@ -52,6 +52,14 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper mFirstData = firstData; } + @Override + public LocalData getLocalData(int dataID) { + if (dataID == 0) { + return mFirstData; + } + return mAdapter.getLocalData(dataID - 1); + } + @Override public void removeData(Context context, int dataID) { if (dataID > 0) { @@ -68,6 +76,28 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper return -1; } + @Override + public void updateData(int pos, LocalData data) { + if (pos == 0) { + mFirstData = data; + if (mListener != null) { + mListener.onDataUpdated(new UpdateReporter() { + @Override + public boolean isDataRemoved(int dataID) { + return false; + } + + @Override + public boolean isDataUpdated(int dataID) { + return (dataID == 0); + } + }); + } + } else { + mAdapter.updateData(pos - 1, data); + } + } + @Override public int getTotalNumber() { return (mAdapter.getTotalNumber() + 1); @@ -99,15 +129,6 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper } } - @Override - public void onDataCentered(int dataID, boolean centered) { - if (dataID != 0) { - mAdapter.onDataCentered(dataID, centered); - } else { - // TODO: notify the data - } - } - @Override public void setListener(Listener listener) { mListener = listener; @@ -132,12 +153,12 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper mListener.onDataUpdated(new UpdateReporter() { @Override public boolean isDataRemoved(int dataID) { - return reporter.isDataRemoved(dataID + 1); + return reporter.isDataRemoved(dataID - 1); } @Override public boolean isDataUpdated(int dataID) { - return reporter.isDataUpdated(dataID + 1); + return reporter.isDataUpdated(dataID - 1); } }); } diff --git a/src/com/android/camera/data/FixedLastDataAdapter.java b/src/com/android/camera/data/FixedLastDataAdapter.java index 16c047d1a..b8325ec72 100644 --- a/src/com/android/camera/data/FixedLastDataAdapter.java +++ b/src/com/android/camera/data/FixedLastDataAdapter.java @@ -29,7 +29,8 @@ import com.android.camera.ui.FilmStripView; */ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper { - private final LocalData mLastData; + private LocalData mLastData; + private Listener mListener; /** * Constructor. @@ -47,6 +48,25 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper { mLastData = lastData; } + @Override + public void setListener(Listener listener) { + super.setListener(listener); + mListener = listener; + } + + @Override + public LocalData getLocalData(int dataID) { + int totalNumber = mAdapter.getTotalNumber(); + + if (dataID < totalNumber) { + return mAdapter.getLocalData(dataID); + } else if (dataID == totalNumber) { + return mLastData; + } + + return null; + } + @Override public void removeData(Context context, int dataID) { if (dataID < mAdapter.getTotalNumber()) { @@ -59,6 +79,30 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper { return mAdapter.findDataByContentUri(uri); } + @Override + public void updateData(final int pos, LocalData data) { + int totalNumber = mAdapter.getTotalNumber(); + + if (pos < totalNumber) { + mAdapter.updateData(pos, data); + } else if (pos == totalNumber) { + mLastData = data; + if (mListener != null) { + mListener.onDataUpdated(new UpdateReporter() { + @Override + public boolean isDataRemoved(int dataID) { + return false; + } + + @Override + public boolean isDataUpdated(int dataID) { + return (dataID == pos); + } + }); + } + } + } + @Override public int getTotalNumber() { return mAdapter.getTotalNumber() + 1; @@ -101,17 +145,6 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper { } } - @Override - public void onDataCentered(int dataID, boolean centered) { - int totalNumber = mAdapter.getTotalNumber(); - - if (dataID < totalNumber) { - mAdapter.onDataCentered(dataID, centered); - } else if (dataID == totalNumber) { - // TODO: notify the data - } - } - @Override public boolean canSwipeInFullScreen(int dataID) { int totalNumber = mAdapter.getTotalNumber(); diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java index eb2212c2a..10cf9aec6 100644 --- a/src/com/android/camera/data/LocalData.java +++ b/src/com/android/camera/data/LocalData.java @@ -32,7 +32,6 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.AsyncTask; import android.provider.MediaStore; -import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; import android.provider.MediaStore.Video.VideoColumns; import android.util.Log; @@ -62,15 +61,65 @@ public interface LocalData extends FilmStripView.ImageData { public static final int ACTION_DELETE = (1 << 1); View getView(Context c, int width, int height, Drawable placeHolder); + + /** + * Gets the date when this data is created. The returned date is also used + * for sorting data. + * + * @return The date when this data is created. + * @see {@link NewestFirstComparator} + */ long getDateTaken(); + + /** + * Gets the date when this data is modified. The returned date is also used + * for sorting data. + * + * @return The date when this data is modified. + * @see {@link NewestFirstComparator} + */ long getDateModified(); + + /** Gets the title of this data */ String getTitle(); - boolean isDataActionSupported(int action); + + /** + * Checks if the data actions (delete/play ...) can be applied on this data. + * + * @param actions The actions to check. + * @return Whether all the actions are supported. + */ + boolean isDataActionSupported(int actions); + boolean delete(Context c); + void onFullScreen(boolean fullScreen); + + /** Returns {@code true} if it allows swipe to filmstrip in full screen. */ boolean canSwipeInFullScreen(); + + /** + * Returns the path to the data on the storage. + * + * @return Empty path if there's none. + */ String getPath(); + /** + * Returns the content URI of this data item. + * + * @return {@code Uri.EMPTY} if not valid. + */ + Uri getContentUri(); + + /** + * Refresh the data content. + * + * @param resolver {@link ContentResolver} to refresh the data. + * @return {@code true} if success, {@code false} otherwise. + */ + boolean refresh(ContentResolver resolver); + static class NewestFirstComparator implements Comparator { /** Compare taken/modified date of LocalData in descent order to make @@ -103,7 +152,7 @@ public interface LocalData extends FilmStripView.ImageData { /** * A base class for all the local media files. The bitmap is loaded in * background thread. Subclasses should implement their own background - * loading thread by subclassing BitmapLoadTask and overriding + * loading thread by sub-classing BitmapLoadTask and overriding * doInBackground() to return a bitmap. */ abstract static class LocalMediaData implements LocalData { @@ -250,14 +299,6 @@ public interface LocalData extends FilmStripView.ImageData { } } - /** - * Returns the content URI of this data item. - */ - private Uri getContentUri() { - Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; - return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); - } - @Override public abstract int getType(); @@ -398,6 +439,32 @@ public interface LocalData extends FilmStripView.ImageData { return super.delete(c); } + @Override + public Uri getContentUri() { + Uri baseUri = CONTENT_URI; + return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); + } + + @Override + public boolean refresh(ContentResolver resolver) { + Cursor c = resolver.query( + getContentUri(), QUERY_PROJECTION, null, null, null); + if (c == null || !c.moveToFirst()) { + return false; + } + Photo newData = buildFromCursor(c); + id = newData.id; + title = newData.title; + mimeType = newData.mimeType; + dateTaken = newData.dateTaken; + dateModified = newData.dateModified; + path = newData.path; + orientation = newData.orientation; + width = newData.width; + height = newData.height; + return true; + } + @Override protected BitmapLoadTask getBitmapLoadTask( ImageView v, int decodeWidth, int decodeHeight) { @@ -492,8 +559,7 @@ public interface LocalData extends FilmStripView.ImageData { d.path = c.getString(COL_DATA); d.width = c.getInt(COL_WIDTH); d.height = c.getInt(COL_HEIGHT); - d.mPlayUri = CONTENT_URI.buildUpon() - .appendPath(String.valueOf(d.id)).build(); + d.mPlayUri = d.getContentUri(); MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(d.path); String rotation = retriever.extractMetadata( @@ -542,6 +608,32 @@ public interface LocalData extends FilmStripView.ImageData { return super.delete(ctx); } + @Override + public Uri getContentUri() { + Uri baseUri = CONTENT_URI; + return baseUri.buildUpon().appendPath(String.valueOf(id)).build(); + } + + @Override + public boolean refresh(ContentResolver resolver) { + Cursor c = resolver.query( + getContentUri(), QUERY_PROJECTION, null, null, null); + if (c == null && !c.moveToFirst()) { + return false; + } + Video newData = buildFromCursor(c); + id = newData.id; + title = newData.title; + mimeType = newData.mimeType; + dateTaken = newData.dateTaken; + dateModified = newData.dateModified; + path = newData.path; + width = newData.width; + height = newData.height; + mPlayUri = newData.mPlayUri; + return true; + } + @Override public View getView(final Context ctx, int decodeWidth, int decodeHeight, Drawable placeHolder) { @@ -665,6 +757,16 @@ public interface LocalData extends FilmStripView.ImageData { return ""; } + @Override + public Uri getContentUri() { + return Uri.EMPTY; + } + + @Override + public boolean refresh(ContentResolver resolver) { + return false; + } + @Override public boolean isUIActionSupported(int action) { return false; diff --git a/src/com/android/camera/data/LocalDataAdapter.java b/src/com/android/camera/data/LocalDataAdapter.java index 3b4f07dea..0a5fde0b5 100644 --- a/src/com/android/camera/data/LocalDataAdapter.java +++ b/src/com/android/camera/data/LocalDataAdapter.java @@ -35,6 +35,14 @@ public interface LocalDataAdapter extends DataAdapter { */ public void requestLoad(ContentResolver resolver); + /** + * Returns the specified {@link LocalData}. + * + * @param dataID The ID of the {@link LocalData} to get. + * @return The {@link LocalData} to get. {@code null} if not available. + */ + public LocalData getLocalData(int dataID); + /** * Remove the data in the local camera folder. * @@ -59,6 +67,14 @@ public interface LocalDataAdapter extends DataAdapter { */ public void addNewPhoto(ContentResolver resolver, Uri uri); + /** + * Refresh the data by {@link Uri}. + * + * @param resolver {@link ContentResolver} used to refresh the data. + * @param uri The {@link Uri} of the data to refresh. + */ + public void refresh(ContentResolver resolver, Uri uri); + /** * Finds the {@link LocalData} of the specified content Uri. * @@ -88,4 +104,15 @@ public interface LocalDataAdapter extends DataAdapter { * @return {@code true} if there are items in the queue, {@code false} otherwise. */ public boolean undoDataRemoval(); + + /** + * Update the data in a specific position. + * + * @param pos The position of the data to be updated. + * @param data The new data. + */ + public void updateData(int pos, LocalData data); + + /** Insert a data. */ + public void insertData(LocalData data); } diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java index 79f58c7be..c21f2b2a2 100644 --- a/src/com/android/camera/ui/FilmStripView.java +++ b/src/com/android/camera/ui/FilmStripView.java @@ -38,7 +38,7 @@ import com.android.camera2.R; public class FilmStripView extends ViewGroup { @SuppressWarnings("unused") - private static final String TAG = "FilmStripView"; + private static final String TAG = "CAM_FilmStripView"; private static final int BUFFER_SIZE = 5; private static final int DURATION_GEOMETRY_ADJUST = 200; @@ -102,7 +102,6 @@ public class FilmStripView extends ViewGroup { public static final int TYPE_NONE = 0; public static final int TYPE_CAMERA_PREVIEW = 1; public static final int TYPE_PHOTO = 2; - public static final int TYPE_VIDEO = 3; // Actions allowed to be performed on the image data. // The actions are defined bit-wise so we can use bit operations like @@ -197,7 +196,7 @@ public class FilmStripView extends ViewGroup { } /** - * An interface which defines the listener for UI actions over + * An interface which defines the listener for data events over * {@link ImageData}. */ public interface Listener { @@ -246,7 +245,7 @@ public class FilmStripView extends ViewGroup { public void suggestViewSizeBound(int w, int h); /** - * Sets the listener for FilmStripView UI actions over the ImageData. + * Sets the listener for data events over the ImageData. * * @param listener The listener to use. */ @@ -262,16 +261,6 @@ public class FilmStripView extends ViewGroup { */ public void onDataFullScreen(int dataID, boolean fullScreen); - /** - * The callback when the item is centered/off-centered. - * TODO: Calls this function actually. - * - * @param dataID The ID of the image data. - * @param centered {@code true} if the data is centered. - * {@code false} otherwise. - */ - public void onDataCentered(int dataID, boolean centered); - /** * Returns {@code true} if the view of the data can be moved by swipe * gesture when in full-screen. @@ -310,6 +299,15 @@ public class FilmStripView extends ViewGroup { * {@code false} */ public void onSwitchMode(boolean toCamera); + + /** + * The callback when the item is centered/off-centered. + * + * @param dataID The ID of the image data. + * @param current {@code true} if the data is the current one. + * {@code false} otherwise. + */ + public void onCurrentDataChanged(int dataID, boolean current); } /** @@ -500,7 +498,7 @@ public class FilmStripView extends ViewGroup { return false; } - public int getCurrentType() { + private int getCurrentType() { if (mDataAdapter == null) { return ImageData.TYPE_NONE; } @@ -653,8 +651,7 @@ public class FilmStripView extends ViewGroup { private void stepIfNeeded() { if (!inFilmStrip() && !inFullScreen()) { // The good timing to step to the next view is when everything is - // not in - // transition. + // not in transition. return; } int nearest = findTheNearestView(mCenterX); @@ -662,6 +659,10 @@ public class FilmStripView extends ViewGroup { if (nearest == -1 || nearest == mCurrentInfo) return; + // Going to change the current info, notify the listener. + if (mListener != null) { + mListener.onCurrentDataChanged(mViewInfo[mCurrentInfo].getID(), false); + } int adjust = nearest - mCurrentInfo; if (adjust > 0) { for (int k = 0; k < adjust; k++) { @@ -690,6 +691,9 @@ public class FilmStripView extends ViewGroup { } } } + if (mListener != null) { + mListener.onCurrentDataChanged(mViewInfo[mCurrentInfo].getID(), true); + } } /** Don't go beyond the bound. */ @@ -741,7 +745,7 @@ public class FilmStripView extends ViewGroup { /** * @return The ID of the current item, or -1. */ - private int getCurrentId() { + public int getCurrentId() { ViewInfo current = mViewInfo[mCurrentInfo]; if (current == null) { return -1; -- cgit v1.2.3