summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--res/layout-land/camera_controls.xml3
-rw-r--r--res/layout-port/camera_controls.xml3
-rw-r--r--res/layout/camera_filmstrip.xml39
-rw-r--r--res/layout/video_module.xml7
-rw-r--r--src/com/android/camera/AnimationManager.java157
-rw-r--r--src/com/android/camera/CameraActivity.java162
-rw-r--r--src/com/android/camera/ImageTaskManager.java2
-rw-r--r--src/com/android/camera/PhotoModule.java55
-rw-r--r--src/com/android/camera/PhotoUI.java65
-rw-r--r--src/com/android/camera/Util.java21
-rw-r--r--src/com/android/camera/VideoModule.java44
-rw-r--r--src/com/android/camera/VideoUI.java38
-rw-r--r--src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java10
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java174
-rw-r--r--src/com/android/camera/data/FixedFirstDataAdapter.java45
-rw-r--r--src/com/android/camera/data/FixedLastDataAdapter.java57
-rw-r--r--src/com/android/camera/data/LocalData.java128
-rw-r--r--src/com/android/camera/data/LocalDataAdapter.java27
-rw-r--r--src/com/android/camera/ui/FilmStripView.java40
20 files changed, 794 insertions, 289 deletions
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" />
- <View
+ <ImageView
android:id="@+id/preview_thumb"
android:visibility="invisible"
android:layout_width="@dimen/capture_size"
android:layout_height="@dimen/capture_size"
+ android:scaleType="centerInside"
android:layout_gravity="top|right" />
</com.android.camera.ui.CameraControls>
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" />
- <View
+ <ImageView
android:id="@+id/preview_thumb"
android:visibility="invisible"
android:layout_width="@dimen/capture_size"
android:layout_height="@dimen/capture_size"
+ android:scaleType="centerInside"
android:layout_gravity="top|right" />
</com.android.camera.ui.CameraControls> \ 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" />
-</FrameLayout> \ No newline at end of file
+ <LinearLayout
+ android:id="@+id/pano_stitching_progress_panel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:paddingBottom="52dp"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:paddingTop="5dp"
+ android:visibility="gone"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/pano_stitching_progress_text"
+ android:text="@string/pano_progress_text"
+ android:textColor="#ffffffff"
+ android:textSize="14dp"
+ android:shadowColor="#ff000000"
+ android:shadowDx="0"
+ android:shadowDy="0"
+ android:shadowRadius="2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:paddingBottom="8dp"
+ android:visibility="visible"
+ android:layout_gravity="right"/>
+
+ <ProgressBar
+ android:id="@+id/pano_stitching_progress_bar"
+ style="@android:style/Widget.Holo.Light.ProgressBar.Horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="visible"
+ android:layout_gravity="bottom|center_horizontal" />
+ </LinearLayout>
+
+</FrameLayout>
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" />
+ <View
+ android:id="@+id/flash_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/white"
+ android:visibility="gone"
+ android:alpha="0" />
<FrameLayout android:id="@+id/preview_border"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/src/com/android/camera/AnimationManager.java b/src/com/android/camera/AnimationManager.java
new file mode 100644
index 000000000..41aa4b862
--- /dev/null
+++ b/src/com/android/camera/AnimationManager.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.View;
+
+/**
+ * Class to handle animations.
+ */
+
+public class AnimationManager {
+
+ public static final float FLASH_ALPHA_START = 0.3f;
+ public static final float FLASH_ALPHA_END = 0f;
+ public static final int FLASH_DURATION = 300;
+
+ public static final int SHRINK_DURATION = 400;
+ public static final int HOLD_DURATION = 2500;
+ public static final int SLIDE_DURATION = 1100;
+
+ private ObjectAnimator mFlashAnim;
+ private AnimatorSet mCaptureAnimator;
+
+ /**
+ * Starts capture animation.
+ * @param view a thumbnail view that shows a picture captured and gets animated
+ */
+ public void startCaptureAnimation(final View view) {
+ if (mCaptureAnimator != null && mCaptureAnimator.isStarted()) {
+ mCaptureAnimator.cancel();
+ }
+ View parentView = (View) view.getParent();
+ float slideDistance = (float) (parentView.getWidth() - view.getLeft());
+
+ float scaleX = ((float) parentView.getWidth()) / ((float) view.getWidth());
+ float scaleY = ((float) parentView.getHeight()) / ((float) view.getHeight());
+ float scale = scaleX > 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<Integer, Void, Bitmap> {
+ 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<String> 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<Integer> 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
@@ -74,6 +74,11 @@ public abstract class AbstractLocalDataAdapterWrapper implements LocalDataAdapte
}
@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 + "%" };
@@ -64,6 +64,15 @@ public class CameraDataAdapter implements LocalDataAdapter {
}
@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) {
return 0;
@@ -73,7 +82,7 @@ public class CameraDataAdapter implements LocalDataAdapter {
@Override
public ImageData getImageData(int id) {
- return getData(id);
+ return getLocalData(id);
}
@Override
@@ -116,11 +125,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) {
return mImages.get(dataID).canSwipeInFullScreen();
@@ -138,49 +142,60 @@ public class CameraDataAdapter implements LocalDataAdapter {
mListener.onDataRemoved(dataID, d);
}
- private void insertData(LocalData data) {
- if (mImages == null) {
- mImages = new ArrayList<LocalData>();
- }
-
- // 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<LocalData> 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<LocalData> 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<LocalData>();
- }
- 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<LocalData>();
+ }
- @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<LocalData> 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<LocalData> 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;
/**
@@ -53,6 +53,14 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper
}
@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) {
mAdapter.removeData(context, dataID - 1);
@@ -69,6 +77,28 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper
}
@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);
}
@@ -100,15 +130,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;
mAdapter.setListener((listener == null) ? null : this);
@@ -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.
@@ -48,6 +49,25 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper {
}
@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()) {
mAdapter.removeData(context, dataID);
@@ -60,6 +80,30 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper {
}
@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;
}
@@ -102,17 +146,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<LocalData> {
/** 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();
@@ -399,6 +440,32 @@ public interface LocalData extends FilmStripView.ImageData {
}
@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) {
return new PhotoBitmapLoadTask(v, decodeWidth, 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(
@@ -543,6 +609,32 @@ public interface LocalData extends FilmStripView.ImageData {
}
@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) {
@@ -666,6 +758,16 @@ public interface LocalData extends FilmStripView.ImageData {
}
@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
@@ -36,6 +36,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.
*
* @param context {@link Context} used to remove the data.
@@ -60,6 +68,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.
*
* @param Uri The content Uri of the {@link LocalData}.
@@ -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.
*/
@@ -263,16 +262,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;