summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--proguard.flags4
-rw-r--r--res/layout/pano_capture.xml1
-rw-r--r--res/layout/pano_control.xml1
-rw-r--r--res/layout/pano_review.xml2
-rw-r--r--src/com/android/camera/panorama/MosaicFrameProcessor.java37
-rw-r--r--src/com/android/camera/panorama/OnClickAttr.java31
-rw-r--r--src/com/android/camera/panorama/PanoramaActivity.java155
7 files changed, 176 insertions, 55 deletions
diff --git a/proguard.flags b/proguard.flags
index 5dcd2b9..a3dc864 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -11,3 +11,7 @@
-keep class com.android.camera.VideoCamera {
public boolean isRecording();
}
+
+-keep class * extends android.app.Activity {
+ @com.android.camera.panorama.OnClickAttr <methods>;
+}
diff --git a/res/layout/pano_capture.xml b/res/layout/pano_capture.xml
index a12e6e6..0ce34b9 100644
--- a/res/layout/pano_capture.xml
+++ b/res/layout/pano_capture.xml
@@ -41,6 +41,7 @@
<Button android:id="@+id/pano_capture_stop_button"
android:text="@string/pano_capture_stop"
+ android:onClick="onStopButtonClicked"
android:textSize="24dp"
android:layout_width="180dp"
android:layout_height="180dp" />
diff --git a/res/layout/pano_control.xml b/res/layout/pano_control.xml
index e3ab1d4..4a1175a 100644
--- a/res/layout/pano_control.xml
+++ b/res/layout/pano_control.xml
@@ -29,6 +29,7 @@
android:layout_centerInParent="true"
android:clickable="true"
android:focusable="true"
+ android:onClick="onShutterButtonClicked"
android:background="@drawable/btn_shutter"/>
<include layout="@layout/mode_picker"/>
diff --git a/res/layout/pano_review.xml b/res/layout/pano_review.xml
index b778daa..40dee1e 100644
--- a/res/layout/pano_review.xml
+++ b/res/layout/pano_review.xml
@@ -39,11 +39,13 @@
<Button android:id="@+id/pano_review_retake_button"
android:text="@string/review_retake"
+ android:onClick="onRetakeButtonClicked"
android:textSize="24dp"
android:layout_width="180dp"
android:layout_height="180dp" />
<Button android:id="@+id/pano_review_ok_button"
android:text="@string/review_ok"
+ android:onClick="onOkButtonClicked"
android:textSize="24dp"
android:layout_width="180dp"
android:layout_height="180dp" />
diff --git a/src/com/android/camera/panorama/MosaicFrameProcessor.java b/src/com/android/camera/panorama/MosaicFrameProcessor.java
index c5eeac5..6496f11 100644
--- a/src/com/android/camera/panorama/MosaicFrameProcessor.java
+++ b/src/com/android/camera/panorama/MosaicFrameProcessor.java
@@ -18,6 +18,9 @@ package com.android.camera.panorama;
import android.util.Log;
+/**
+ * Class to handle the processing of each frame by Mosaicer.
+ */
public class MosaicFrameProcessor {
private static final boolean LOGV = true;
private static final String TAG = "MosaicFrameProcessor";
@@ -54,7 +57,6 @@ public class MosaicFrameProcessor {
private int mPreviewHeight;
private int mPreviewBufferSize;
-
public interface ProgressListener {
public void onProgress(boolean isFinished, float translationRate,
int traversedAngleX, int traversedAngleY);
@@ -72,14 +74,20 @@ public class MosaicFrameProcessor {
mProgressListener = listener;
}
- public void onResume() {
+ public void initialize() {
setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);
+ reset();
}
- public void onPause() {
- releaseMosaicer();
+ public void clear() {
+ mMosaicer.freeMosaicMemory();
+
+ for (int i = 0; i < NUM_FRAMES_IN_BUFFER; i++) {
+ mFrames[i] = null;
+ }
}
+
private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
@@ -94,12 +102,15 @@ public class MosaicFrameProcessor {
}
}
- private void releaseMosaicer() {
- mMosaicer.freeMosaicMemory();
-
- for (int i = 0; i < NUM_FRAMES_IN_BUFFER; i++) {
- mFrames[i] = null;
- }
+ public void reset() {
+ // reset() can be called even if MosaicFrameProcessor is not initialized.
+ // Only counters will be changed.
+ mTotalFrameCount = 0;
+ mFillIn = 0;
+ mLastProcessedFrameTimestamp = 0;
+ mLastProcessFrameIdx = -1;
+ mCurrProcessFrameIdx = -1;
+ mMosaicer.reset();
}
public void createMosaic(boolean highRes) {
@@ -114,6 +125,12 @@ public class MosaicFrameProcessor {
// updates the UI to show progress.
// When done, processes and displays the final mosaic.
public void processFrame(byte[] data) {
+ if (mFrames[mFillIn] == null) {
+ // clear() is called and buffers are cleared, stop computation.
+ // This can happen when the onPause() is called in the activity, but still some frames
+ // are not processed yet and thus the callback may be invoked.
+ return;
+ }
long t1 = System.currentTimeMillis();
mFrameTimestamp[mFillIn] = t1;
System.arraycopy(data, 0, mFrames[mFillIn], 0, data.length);
diff --git a/src/com/android/camera/panorama/OnClickAttr.java b/src/com/android/camera/panorama/OnClickAttr.java
new file mode 100644
index 0000000..bfc7c4e
--- /dev/null
+++ b/src/com/android/camera/panorama/OnClickAttr.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 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.panorama;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Interface for OnClickAttr annotation.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnClickAttr {
+}
diff --git a/src/com/android/camera/panorama/PanoramaActivity.java b/src/com/android/camera/panorama/PanoramaActivity.java
index 11fb6d1..0b1ec60 100644
--- a/src/com/android/camera/panorama/PanoramaActivity.java
+++ b/src/com/android/camera/panorama/PanoramaActivity.java
@@ -28,6 +28,8 @@ import com.android.camera.Util;
import android.app.Activity;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -39,7 +41,6 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -48,7 +49,6 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
-import android.widget.Button;
import android.widget.ImageView;
import java.io.ByteArrayOutputStream;
@@ -64,13 +64,16 @@ public class PanoramaActivity extends Activity implements
public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
private static final int MSG_FINAL_MOSAIC_READY = 1;
+ private static final int MSG_RESET_TO_PREVIEW = 2;
private static final String TAG = "PanoramaActivity";
private static final int PREVIEW_STOPPED = 0;
private static final int PREVIEW_ACTIVE = 1;
-
// Ratio of nanosecond to second
private static final float NS2S = 1.0f / 1000000000.0f;
+
+ private boolean mPausing;
+
private View mPanoControlLayout;
private View mCaptureLayout;
private View mReviewLayout;
@@ -78,9 +81,10 @@ public class PanoramaActivity extends Activity implements
private ImageView mReview;
private CaptureView mCaptureView;
private MosaicRendererSurfaceView mRealTimeMosaicView;
-
private ShutterButton mShutterButton;
- private Button mStopButton;
+
+ private byte[] mFinalJpegData;
+
private int mPreviewWidth;
private int mPreviewHeight;
private Camera mCameraDevice;
@@ -94,6 +98,8 @@ public class PanoramaActivity extends Activity implements
private Handler mMainHandler;
private SurfaceHolder mSurfaceHolder;
+ private boolean mThreadRunning;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -114,9 +120,15 @@ public class PanoramaActivity extends Activity implements
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FINAL_MOSAIC_READY:
- Uri uri = (Uri) msg.obj;
- showFinalMosaic(uri);
+ mThreadRunning = false;
+ showFinalMosaic((Bitmap) msg.obj);
+ break;
+ case MSG_RESET_TO_PREVIEW:
+ mThreadRunning = false;
+ resetToPreview();
+ break;
}
+ clearMosaicFrameProcessorIfNeeded();
}
};
}
@@ -130,6 +142,7 @@ public class PanoramaActivity extends Activity implements
private void releaseCamera() {
if (mCameraDevice != null) {
+ mCameraDevice.setPreviewCallbackWithBuffer(null);
CameraHolder.instance().release();
mCameraDevice = null;
mCameraState = PREVIEW_STOPPED;
@@ -267,23 +280,25 @@ public class PanoramaActivity extends Activity implements
});
mCaptureLayout.setVisibility(View.VISIBLE);
- mPreview.setVisibility(View.GONE);
+ mPreview.setVisibility(View.INVISIBLE); // will be re-used, invisible is better than gone.
mRealTimeMosaicView.setVisibility(View.VISIBLE);
mPanoControlLayout.setVisibility(View.GONE);
}
private void stopCapture() {
mMosaicFrameProcessor.setProgressListener(null);
- mCameraDevice.stopPreview();
- mCameraDevice.setPreviewCallbackWithBuffer(null);
+ stopPreview();
// TODO: show some dialog for long computation.
- Thread t = new Thread() {
- @Override
- public void run() {
- generateAndStoreFinalMosaic(false);
- }
- };
- t.start();
+ if (!mThreadRunning) {
+ mThreadRunning = true;
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ generateAndStoreFinalMosaic(false);
+ }
+ };
+ t.start();
+ }
}
private void updateProgress(float translationRate, int traversedAngleX, int traversedAngleY) {
@@ -323,16 +338,10 @@ public class PanoramaActivity extends Activity implements
mShutterButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ if (mPausing || mThreadRunning) return;
startCapture();
}
});
- mStopButton = (Button) findViewById(R.id.pano_capture_stop_button);
- mStopButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- stopCapture();
- }
- });
mPanoControlLayout = (View) findViewById(R.id.pano_control_layout);
@@ -342,23 +351,85 @@ public class PanoramaActivity extends Activity implements
mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA);
}
- private void showFinalMosaic(Uri uri) {
- mReview.setImageURI(uri);
- mCaptureLayout.setVisibility(View.INVISIBLE);
- mPreview.setVisibility(View.INVISIBLE);
- mReviewLayout.setVisibility(View.VISIBLE);
- mCaptureView.setStatusText("");
- mCaptureView.setSweepAngle(0);
+ @OnClickAttr
+ public void onStopButtonClicked(View v) {
+ if (mPausing || mThreadRunning) return;
+ stopCapture();
+ }
+
+ @OnClickAttr
+ public void onOkButtonClicked(View v) {
+ if (mPausing || mThreadRunning) return;
+ mThreadRunning = true;
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ saveFinalMosaic();
+ }
+ };
+ t.start();
+ }
+
+ @OnClickAttr
+ public void onRetakeButtonClicked(View v) {
+ if (mPausing || mThreadRunning) return;
+ resetToPreview();
+ }
+
+ private void resetToPreview() {
+ mPreview.setVisibility(View.VISIBLE);
+ mPanoControlLayout.setVisibility(View.VISIBLE);
+ mRealTimeMosaicView.setVisibility(View.GONE);
+ mCaptureLayout.setVisibility(View.GONE);
+ mReviewLayout.setVisibility(View.GONE);
+ mMosaicFrameProcessor.reset();
+ if (!mPausing) startPreview();
+ }
+
+ private void showFinalMosaic(Bitmap bitmap) {
+ if (bitmap != null) {
+ mReview.setImageBitmap(bitmap);
+ mCaptureLayout.setVisibility(View.GONE);
+ mPreview.setVisibility(View.INVISIBLE);
+ mReviewLayout.setVisibility(View.VISIBLE);
+ mCaptureView.setStatusText("");
+ mCaptureView.setSweepAngle(0);
+ }
+ }
+
+ private void saveFinalMosaic() {
+ if (mFinalJpegData != null) {
+ Storage.addImage(getContentResolver(), mCurrentImagePath, mTimeTaken, null, 0,
+ mFinalJpegData);
+ mFinalJpegData = null;
+ }
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW));
+ }
+
+ private void clearMosaicFrameProcessorIfNeeded() {
+ if (!mPausing || mThreadRunning) return;
+ mMosaicFrameProcessor.clear();
+ }
+
+ private void initMosaicFrameProcessorIfNeeded() {
+ if (mPausing || mThreadRunning) return;
+ if (mMosaicFrameProcessor == null) {
+ // Start the activity for the first time.
+ mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5,
+ mPreviewWidth, mPreviewHeight, getPreviewBufSize());
+ }
+ mMosaicFrameProcessor.initialize();
}
@Override
protected void onPause() {
super.onPause();
releaseCamera();
- mMosaicFrameProcessor.onPause();
+ mPausing = true;
mCaptureView.onPause();
mRealTimeMosaicView.onPause();
mSensorManager.unregisterListener(mListener);
+ clearMosaicFrameProcessorIfNeeded();
System.gc();
}
@@ -366,6 +437,7 @@ public class PanoramaActivity extends Activity implements
protected void onResume() {
super.onResume();
+ mPausing = false;
/*
* It is not necessary to get accelerometer events at a very high rate,
* by using a slower rate (SENSOR_DELAY_UI), we get an automatic
@@ -376,16 +448,10 @@ public class PanoramaActivity extends Activity implements
mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI);
setupCamera();
+ // Camera must be initialized before MosaicFrameProcessor is initialized. The preview size
+ // has to be decided by camera device.
+ initMosaicFrameProcessorIfNeeded();
startPreview();
-
- if (mMosaicFrameProcessor == null) {
- // Start the activity for the first time.
- mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5,
- mPreviewWidth, mPreviewHeight, getPreviewBufSize());
- mMosaicFrameProcessor.onResume();
- } else {
- mMosaicFrameProcessor.onResume();
- }
mCaptureView.onResume();
mRealTimeMosaicView.onResume();
}
@@ -449,11 +515,10 @@ public class PanoramaActivity extends Activity implements
Log.e(TAG, "Exception in storing final mosaic", e);
return;
}
- Uri uri = Storage.addImage(
- getContentResolver(), mCurrentImagePath, mTimeTaken, null, 0,
- out.toByteArray());
- mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_FINAL_MOSAIC_READY, uri));
+ mFinalJpegData = out.toByteArray();
+ Bitmap bitmap = BitmapFactory.decodeByteArray(mFinalJpegData, 0, mFinalJpegData.length);
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_FINAL_MOSAIC_READY, bitmap));
// Now's a good time to run the GC. Since we won't do any explicit
// allocation during the test, the GC should stay dormant and not
// influence our results.