PanoramaActivity.java revision 22d4b7fbace56c03092eb088a4df98e04a6681af
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.panorama;
18
19import com.android.camera.CameraDisabledException;
20import com.android.camera.CameraHardwareException;
21import com.android.camera.CameraHolder;
22import com.android.camera.Exif;
23import com.android.camera.MenuHelper;
24import com.android.camera.ModePicker;
25import com.android.camera.OnClickAttr;
26import com.android.camera.R;
27import com.android.camera.Storage;
28import com.android.camera.Thumbnail;
29import com.android.camera.Util;
30import com.android.camera.ui.RotateImageView;
31import com.android.camera.ui.SharePopup;
32
33import android.app.Activity;
34import android.app.AlertDialog;
35import android.app.ProgressDialog;
36import android.content.Context;
37import android.content.DialogInterface;
38import android.graphics.Bitmap;
39import android.graphics.BitmapFactory;
40import android.graphics.ImageFormat;
41import android.graphics.PixelFormat;
42import android.graphics.Rect;
43import android.graphics.SurfaceTexture;
44import android.graphics.YuvImage;
45import android.hardware.Camera;
46import android.hardware.Camera.Parameters;
47import android.hardware.Camera.Size;
48import android.hardware.Sensor;
49import android.hardware.SensorEvent;
50import android.hardware.SensorEventListener;
51import android.hardware.SensorManager;
52import android.net.Uri;
53import android.os.Bundle;
54import android.os.Handler;
55import android.os.Message;
56import android.util.Log;
57import android.view.Gravity;
58import android.view.View;
59import android.view.WindowManager;
60import android.view.animation.Animation;
61import android.view.animation.AnimationUtils;
62import android.widget.Button;
63import android.widget.ImageView;
64import android.widget.TextView;
65
66import java.io.ByteArrayOutputStream;
67import java.util.List;
68
69/**
70 * Activity to handle panorama capturing.
71 */
72public class PanoramaActivity extends Activity implements
73        ModePicker.OnModeChangeListener,
74        SurfaceTexture.OnFrameAvailableListener,
75        MosaicRendererSurfaceViewRenderer.MosaicSurfaceCreateListener {
76    public static final int DEFAULT_SWEEP_ANGLE = 160;
77    public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
78    public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
79
80    private static final int MSG_FINAL_MOSAIC_READY = 1;
81    private static final int MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL = 2;
82    private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 3;
83    private static final int MSG_DISMISS_ALERT_DIALOG_AND_RESET_TO_PREVIEW = 4;
84
85    private static final String TAG = "PanoramaActivity";
86    private static final int PREVIEW_STOPPED = 0;
87    private static final int PREVIEW_ACTIVE = 1;
88    private static final int CAPTURE_VIEWFINDER = 0;
89    private static final int CAPTURE_MOSAIC = 1;
90
91    // Speed is in unit of deg/sec
92    private static final float PANNING_SPEED_THRESHOLD = 30f;
93
94    // Ratio of nanosecond to second
95    private static final float NS2S = 1.0f / 1000000000.0f;
96
97    private boolean mPausing;
98
99    private View mPanoControlLayout;
100    private View mPanoLayout;
101    private View mCaptureLayout;
102    private Button mStopCaptureButton;
103    private View mReviewLayout;
104    private ImageView mReview;
105    private CaptureView mCaptureView;
106    private MosaicRendererSurfaceView mMosaicView;
107    private TextView mTooFastPrompt;
108    private Animation mSlideIn, mSlideOut;
109
110    private ProgressDialog mProgressDialog;
111    private String mPreparePreviewString;
112    private String mGeneratePanoramaString;
113    private String mDialogTitle;
114    private String mDialogOk;
115    private AlertDialog mAlertDialog;
116
117    private RotateImageView mThumbnailView;
118    private Thumbnail mThumbnail;
119    private SharePopup mSharePopup;
120
121    private int mPreviewWidth;
122    private int mPreviewHeight;
123    private Camera mCameraDevice;
124    private int mCameraState;
125    private int mCaptureState;
126    private SensorManager mSensorManager;
127    private Sensor mSensor;
128    private ModePicker mModePicker;
129    private MosaicFrameProcessor mMosaicFrameProcessor;
130    private String mCurrentImagePath = null;
131    private long mTimeTaken;
132    private Handler mMainHandler;
133    private SurfaceTexture mSurfaceTexture;
134    private boolean mThreadRunning;
135    private float[] mTransformMatrix;
136    private float mHorizontalViewAngle;
137    private float mVerticalViewAngle;
138
139    private class MosaicJpeg {
140        public MosaicJpeg(byte[] data, int width, int height) {
141            this.data = data;
142            this.width = width;
143            this.height = height;
144        }
145
146        public final byte[] data;
147        public final int width;
148        public final int height;
149    }
150
151    @Override
152    public void onCreate(Bundle icicle) {
153        super.onCreate(icicle);
154
155        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
156                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
157
158        createContentView();
159
160        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
161        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
162        if (mSensor == null) {
163            mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
164        }
165
166        mTransformMatrix = new float[16];
167
168        mPreparePreviewString =
169                getResources().getString(R.string.pano_dialog_prepare_preview);
170        mGeneratePanoramaString =
171                getResources().getString(R.string.pano_dialog_generate_panorama);
172        mDialogTitle = getResources().getString(R.string.pano_dialog_title);
173        mDialogOk = getResources().getString(R.string.dialog_ok);
174
175        Context context = getApplicationContext();
176        mSlideIn = AnimationUtils.loadAnimation(context, R.anim.slide_in_from_right);
177        mSlideOut = AnimationUtils.loadAnimation(context, R.anim.slide_out_to_right);
178
179        mMainHandler = new Handler() {
180            @Override
181            public void handleMessage(Message msg) {
182                switch (msg.what) {
183                    case MSG_FINAL_MOSAIC_READY:
184                        onBackgroundThreadFinished();
185                        showFinalMosaic((Bitmap) msg.obj);
186                        break;
187                    case MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL:
188                        onBackgroundThreadFinished();
189                        // Set the thumbnail bitmap here because mThumbnailView must be accessed
190                        // from the UI thread.
191                        if (mThumbnail != null) {
192                            mThumbnailView.setBitmap(mThumbnail.getBitmap());
193                        }
194                        resetToPreview();
195                        break;
196                    case MSG_GENERATE_FINAL_MOSAIC_ERROR:
197                        onBackgroundThreadFinished();
198                        mAlertDialog = (new AlertDialog.Builder(PanoramaActivity.this))
199                                .setTitle(mDialogTitle)
200                                .setMessage(R.string.pano_dialog_panorama_failed)
201                                .create();
202                        mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, mDialogOk,
203                                obtainMessage(MSG_DISMISS_ALERT_DIALOG_AND_RESET_TO_PREVIEW));
204                        mAlertDialog.show();
205                        break;
206                    case MSG_DISMISS_ALERT_DIALOG_AND_RESET_TO_PREVIEW:
207                        mAlertDialog.dismiss();
208                        mAlertDialog = null;
209                        resetToPreview();
210                        break;
211                }
212                clearMosaicFrameProcessorIfNeeded();
213            }
214        };
215    }
216
217    private void setupCamera() {
218        openCamera();
219        Parameters parameters = mCameraDevice.getParameters();
220        setupCaptureParams(parameters);
221        configureCamera(parameters);
222    }
223
224    private void releaseCamera() {
225        if (mCameraDevice != null) {
226            mCameraDevice.setPreviewCallbackWithBuffer(null);
227            CameraHolder.instance().release();
228            mCameraDevice = null;
229            mCameraState = PREVIEW_STOPPED;
230        }
231    }
232
233    private void openCamera() {
234        try {
235            mCameraDevice = Util.openCamera(this, CameraHolder.instance().getBackCameraId());
236        } catch (CameraHardwareException e) {
237            Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
238            return;
239        } catch (CameraDisabledException e) {
240            Util.showErrorAndFinish(this, R.string.camera_disabled);
241            return;
242        }
243    }
244
245    private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
246            boolean needSmaller) {
247        int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
248        boolean hasFound = false;
249        for (Size size : supportedSizes) {
250            int h = size.height;
251            int w = size.width;
252            // we only want 4:3 format.
253            int d = DEFAULT_CAPTURE_PIXELS - h * w;
254            if (needSmaller && d < 0) { // no bigger preview than 960x720.
255                continue;
256            }
257            if (need4To3 && (h * 4 != w * 3)) {
258                continue;
259            }
260            d = Math.abs(d);
261            if (d < pixelsDiff) {
262                mPreviewWidth = w;
263                mPreviewHeight = h;
264                pixelsDiff = d;
265                hasFound = true;
266            }
267        }
268        return hasFound;
269    }
270
271    private void setupCaptureParams(Parameters parameters) {
272        List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
273        if (!findBestPreviewSize(supportedSizes, true, true)) {
274            Log.w(TAG, "No 4:3 ratio preview size supported.");
275            if (!findBestPreviewSize(supportedSizes, false, true)) {
276                Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
277                findBestPreviewSize(supportedSizes, false, false);
278            }
279        }
280        Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth);
281        parameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
282
283        List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
284        int last = frameRates.size() - 1;
285        int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
286        int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
287        parameters.setPreviewFpsRange(minFps, maxFps);
288        Log.v(TAG, "preview fps: " + minFps + ", " + maxFps);
289
290        parameters.setRecordingHint(false);
291
292        mHorizontalViewAngle = parameters.getHorizontalViewAngle();
293        mVerticalViewAngle = parameters.getVerticalViewAngle();
294    }
295
296    public int getPreviewBufSize() {
297        PixelFormat pixelInfo = new PixelFormat();
298        PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
299        // TODO: remove this extra 32 byte after the driver bug is fixed.
300        return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
301    }
302
303    private void configureCamera(Parameters parameters) {
304        mCameraDevice.setParameters(parameters);
305
306        int orientation = Util.getDisplayOrientation(Util.getDisplayRotation(this),
307                CameraHolder.instance().getBackCameraId());
308        mCameraDevice.setDisplayOrientation(orientation);
309    }
310
311    private boolean switchToOtherMode(int mode) {
312        if (isFinishing()) {
313            return false;
314        }
315        MenuHelper.gotoMode(mode, this);
316        finish();
317        return true;
318    }
319
320    public boolean onModeChanged(int mode) {
321        if (mode != ModePicker.MODE_PANORAMA) {
322            return switchToOtherMode(mode);
323        } else {
324            return true;
325        }
326    }
327
328    @Override
329    public void onMosaicSurfaceCreated(final int textureID) {
330        runOnUiThread(new Runnable() {
331            @Override
332            public void run() {
333                if (mSurfaceTexture != null) {
334                    mSurfaceTexture.release();
335                }
336                mSurfaceTexture = new SurfaceTexture(textureID);
337                if (!mPausing) {
338                    mSurfaceTexture.setOnFrameAvailableListener(PanoramaActivity.this);
339                    startCameraPreview();
340                }
341            }
342        });
343    }
344
345    public void runViewFinder() {
346        mMosaicView.setWarping(false);
347        // Call preprocess to render it to low-res and high-res RGB textures.
348        mMosaicView.preprocess(mTransformMatrix);
349        mMosaicView.setReady();
350        mMosaicView.requestRender();
351    }
352
353    public void runMosaicCapture() {
354        mMosaicView.setWarping(true);
355        // Call preprocess to render it to low-res and high-res RGB textures.
356        mMosaicView.preprocess(mTransformMatrix);
357        // Lock the conditional variable to ensure the order of transferGPUtoCPU and
358        // mMosaicFrame.processFrame().
359        mMosaicView.lockPreviewReadyFlag();
360        // Now, transfer the textures from GPU to CPU memory for processing
361        mMosaicView.transferGPUtoCPU();
362        // Wait on the condition variable (will be opened when GPU->CPU transfer is done).
363        mMosaicView.waitUntilPreviewReady();
364        mMosaicFrameProcessor.processFrame();
365    }
366
367    public synchronized void onFrameAvailable(SurfaceTexture surface) {
368        /* This function may be called by some random thread,
369         * so let's be safe and use synchronize. No OpenGL calls can be done here.
370         */
371        // Updating the texture should be done in the GL thread which mMosaicView is attached.
372        mMosaicView.queueEvent(new Runnable() {
373            @Override
374            public void run() {
375                mSurfaceTexture.updateTexImage();
376                mSurfaceTexture.getTransformMatrix(mTransformMatrix);
377            }
378        });
379        // Update the transformation matrix for mosaic pre-process.
380        if (mCaptureState == CAPTURE_VIEWFINDER) {
381            runViewFinder();
382        } else {
383            runMosaicCapture();
384        }
385    }
386
387    public void startCapture() {
388        // Reset values so we can do this again.
389        mTimeTaken = System.currentTimeMillis();
390        mCaptureState = CAPTURE_MOSAIC;
391        mPanoControlLayout.startAnimation(mSlideOut);
392        mPanoControlLayout.setVisibility(View.GONE);
393
394        mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
395            @Override
396            public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
397                    int traversedAngleX, int traversedAngleY) {
398                if (isFinished) {
399                    stopCapture();
400                } else {
401                    updateProgress(panningRateX, panningRateY, traversedAngleX, traversedAngleY);
402                }
403            }
404        });
405
406        mStopCaptureButton.setVisibility(View.VISIBLE);
407        mCaptureView.setVisibility(View.VISIBLE);
408        mMosaicView.setVisibility(View.VISIBLE);
409    }
410
411    private void stopCapture() {
412        mCaptureState = CAPTURE_VIEWFINDER;
413        mTooFastPrompt.setVisibility(View.GONE);
414
415        mMosaicFrameProcessor.setProgressListener(null);
416        stopCameraPreview();
417
418        mSurfaceTexture.setOnFrameAvailableListener(null);
419
420        if (!mThreadRunning) {
421            runBackgroundThreadAndShowDialog(mPreparePreviewString, false, new Thread() {
422                @Override
423                public void run() {
424                    MosaicJpeg jpeg = generateFinalMosaic(false);
425                    Bitmap bitmap = null;
426                    if (jpeg != null) {
427                        bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
428                    }
429                    mMainHandler.sendMessage(mMainHandler.obtainMessage(
430                            MSG_FINAL_MOSAIC_READY, bitmap));
431                }
432            });
433            reportProgress(false);
434        }
435    }
436
437    private void updateProgress(float panningRateX, float panningRateY,
438            int traversedAngleX, int traversedAngleY) {
439
440        mMosaicView.setReady();
441        mMosaicView.requestRender();
442
443        // TODO: Now we just display warning message by the panning speed.
444        // Since we only support horizontal panning, we should display a warning message
445        // in UI when there're significant vertical movements.
446        if ((panningRateX * mHorizontalViewAngle > PANNING_SPEED_THRESHOLD)
447                || (panningRateY * mVerticalViewAngle > PANNING_SPEED_THRESHOLD)) {
448            // TODO: draw speed indication according to the UI spec.
449            mTooFastPrompt.setVisibility(View.VISIBLE);
450            mCaptureView.setSweepAngle(Math.max(traversedAngleX, traversedAngleY) + 1);
451            mCaptureView.invalidate();
452        } else {
453            mTooFastPrompt.setVisibility(View.GONE);
454            mCaptureView.setSweepAngle(Math.max(traversedAngleX, traversedAngleY) + 1);
455            mCaptureView.invalidate();
456        }
457    }
458
459    private void createContentView() {
460        setContentView(R.layout.panorama);
461
462        mCaptureState = CAPTURE_VIEWFINDER;
463
464        mCaptureLayout = (View) findViewById(R.id.pano_capture_layout);
465        mCaptureView = (CaptureView) findViewById(R.id.pano_capture_view);
466        mCaptureView.setStartAngle(-DEFAULT_SWEEP_ANGLE / 2);
467        mStopCaptureButton = (Button) findViewById(R.id.pano_capture_stop_button);
468        mTooFastPrompt = (TextView) findViewById(R.id.pano_capture_too_fast_textview);
469
470        mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
471
472        mReviewLayout = (View) findViewById(R.id.pano_review_layout);
473        mReview = (ImageView) findViewById(R.id.pano_reviewarea);
474        mMosaicView = (MosaicRendererSurfaceView) findViewById(R.id.pano_renderer);
475        mMosaicView.getRenderer().setMosaicSurfaceCreateListener(this);
476        mMosaicView.setVisibility(View.VISIBLE);
477
478        mPanoControlLayout = (View) findViewById(R.id.pano_control_layout);
479        mModePicker = (ModePicker) findViewById(R.id.mode_picker);
480        mModePicker.setVisibility(View.VISIBLE);
481        mModePicker.setOnModeChangeListener(this);
482        mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA);
483
484        mPanoLayout = findViewById(R.id.pano_layout);
485    }
486
487    @OnClickAttr
488    public void onStartButtonClicked(View v) {
489        // If mSurfaceTexture == null then GL setup is not finished yet.
490        // No buttons can be pressed.
491        if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
492        startCapture();
493    }
494
495    @OnClickAttr
496    public void onStopButtonClicked(View v) {
497        if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
498        stopCapture();
499    }
500
501    public void reportProgress(final boolean highRes) {
502        Thread t = new Thread() {
503            @Override
504            public void run() {
505                while (mThreadRunning) {
506                    final int progress = mMosaicFrameProcessor.reportProgress(highRes);
507
508                    try {
509                        Thread.sleep(50);
510                    } catch (Exception e) {
511                        throw new RuntimeException("Panorama reportProgress failed", e);
512                    }
513                    // Update the progress bar
514                    runOnUiThread(new Runnable() {
515                        public void run() {
516                            // Check if mProgressDialog is null because the background thread
517                            // finished.
518                            if (mProgressDialog != null) {
519                                mProgressDialog.setProgress(progress);
520                            }
521                        }
522                    });
523                }
524            }
525        };
526        t.start();
527    }
528
529    @OnClickAttr
530    public void onOkButtonClicked(View v) {
531        if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
532        runBackgroundThreadAndShowDialog(mGeneratePanoramaString, true, new Thread() {
533            @Override
534            public void run() {
535                MosaicJpeg jpeg = generateFinalMosaic(true);
536                if (jpeg == null) {
537                    mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
538                } else {
539                    int orientation = Exif.getOrientation(jpeg.data);
540                    Uri uri = savePanorama(jpeg.data, orientation);
541                    if (uri != null) {
542                        // Create a thumbnail whose width is equal or bigger than the entire screen.
543                        int ratio = (int) Math.ceil((double) jpeg.width / mPanoLayout.getWidth());
544                        int inSampleSize = Integer.highestOneBit(ratio);
545                        mThumbnail = Thumbnail.createThumbnail(
546                                jpeg.data, orientation, inSampleSize, uri);
547                    }
548                    mMainHandler.sendMessage(
549                            mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL));
550                }
551            }
552        });
553        reportProgress(true);
554    }
555
556    /**
557     * If the style is horizontal one, the maximum progress is assumed to be 100.
558     */
559    private void runBackgroundThreadAndShowDialog(
560            String str, boolean showPercentageProgress, Thread thread) {
561        mThreadRunning = true;
562        mProgressDialog = new ProgressDialog(this);
563        if (showPercentageProgress) {
564            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
565            mProgressDialog.setMax(100);
566        }
567        mProgressDialog.setMessage(str);
568        mProgressDialog.show();
569        thread.start();
570    }
571
572    private void onBackgroundThreadFinished() {
573        mThreadRunning = false;
574        mProgressDialog.dismiss();
575        mProgressDialog = null;
576    }
577
578    @OnClickAttr
579    public void onRetakeButtonClicked(View v) {
580        if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
581        resetToPreview();
582    }
583
584    @OnClickAttr
585    public void onThumbnailClicked(View v) {
586        if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
587        showSharePopup();
588    }
589
590    private void showSharePopup() {
591        if (mThumbnail == null) return;
592        Uri uri = mThumbnail.getUri();
593        if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
594            // The orientation compensation is set to 0 here because we only support landscape.
595            // Panorama picture is long. Use pano_layout so the share popup can be full-screen.
596            mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(), "image/jpeg",
597                    0, findViewById(R.id.pano_layout));
598        }
599        mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
600    }
601
602    private void resetToPreview() {
603        mCaptureState = CAPTURE_VIEWFINDER;
604
605        mReviewLayout.setVisibility(View.GONE);
606        mStopCaptureButton.setVisibility(View.GONE);
607        mCaptureView.setVisibility(View.GONE);
608        mPanoControlLayout.setVisibility(View.VISIBLE);
609        mPanoControlLayout.startAnimation(mSlideIn);
610        mCaptureLayout.setVisibility(View.VISIBLE);
611        mMosaicFrameProcessor.reset();
612
613        mSurfaceTexture.setOnFrameAvailableListener(this);
614
615        if (!mPausing) startCameraPreview();
616
617        mMosaicView.setVisibility(View.VISIBLE);
618    }
619
620    private void showFinalMosaic(Bitmap bitmap) {
621        if (bitmap != null) {
622            mReview.setImageBitmap(bitmap);
623        }
624        mCaptureLayout.setVisibility(View.GONE);
625        mReviewLayout.setVisibility(View.VISIBLE);
626        mCaptureView.setSweepAngle(0);
627    }
628
629    private Uri savePanorama(byte[] jpegData, int orientation) {
630        if (jpegData != null) {
631            String imagePath = PanoUtil.createName(
632                    getResources().getString(R.string.pano_file_name_format), mTimeTaken);
633            return Storage.addImage(getContentResolver(), imagePath, mTimeTaken, null,
634                    orientation, jpegData);
635        }
636        return null;
637    }
638
639    private void clearMosaicFrameProcessorIfNeeded() {
640        if (!mPausing || mThreadRunning) return;
641        mMosaicFrameProcessor.clear();
642    }
643
644    private void initMosaicFrameProcessorIfNeeded() {
645        if (mPausing || mThreadRunning) return;
646        if (mMosaicFrameProcessor == null) {
647            // Start the activity for the first time.
648            mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5,
649                    mPreviewWidth, mPreviewHeight, getPreviewBufSize());
650        }
651        mMosaicFrameProcessor.initialize();
652    }
653
654    @Override
655    protected void onPause() {
656        super.onPause();
657
658        releaseCamera();
659        mPausing = true;
660        mMosaicView.onPause();
661        mSensorManager.unregisterListener(mListener);
662        clearMosaicFrameProcessorIfNeeded();
663        System.gc();
664    }
665
666    @Override
667    protected void onResume() {
668        super.onResume();
669
670        mPausing = false;
671        /*
672         * It is not necessary to get accelerometer events at a very high rate,
673         * by using a slower rate (SENSOR_DELAY_UI), we get an automatic
674         * low-pass filter, which "extracts" the gravity component of the
675         * acceleration. As an added benefit, we use less power and CPU
676         * resources.
677         */
678        mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI);
679        mCaptureState = CAPTURE_VIEWFINDER;
680        setupCamera();
681        if (mSurfaceTexture != null) {
682            mSurfaceTexture.setOnFrameAvailableListener(this);
683            startCameraPreview();
684        }
685        // Camera must be initialized before MosaicFrameProcessor is initialized. The preview size
686        // has to be decided by camera device.
687        initMosaicFrameProcessorIfNeeded();
688        mMosaicView.onResume();
689    }
690
691    private final SensorEventListener mListener = new SensorEventListener() {
692        private float mCompassCurrX; // degrees
693        private float mCompassCurrY; // degrees
694        private float mTimestamp;
695
696        public void onSensorChanged(SensorEvent event) {
697            if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
698                if (mTimestamp != 0) {
699                    final float dT = (event.timestamp - mTimestamp) * NS2S;
700                    mCompassCurrX += event.values[1] * dT * 180.0f / Math.PI;
701                    mCompassCurrY += event.values[0] * dT * 180.0f / Math.PI;
702                }
703                mTimestamp = event.timestamp;
704
705            } else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
706                mCompassCurrX = event.values[0];
707                mCompassCurrY = event.values[1];
708            }
709
710            if (mMosaicFrameProcessor != null) {
711                mMosaicFrameProcessor.updateCompassValue(mCompassCurrX, mCompassCurrY);
712            }
713        }
714
715        @Override
716        public void onAccuracyChanged(Sensor sensor, int accuracy) {
717        }
718    };
719
720    public MosaicJpeg generateFinalMosaic(boolean highRes) {
721        mMosaicFrameProcessor.createMosaic(highRes);
722
723        byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
724        if (imageData == null) {
725            Log.e(TAG, "getFinalMosaicNV21() returned null.");
726            return null;
727        }
728
729        int len = imageData.length - 8;
730        int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
731                + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
732        int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
733                + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
734        Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
735
736        if (width <= 0 || height <= 0) {
737            // TODO: pop up a error meesage indicating that the final result is not generated.
738            Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
739                    height);
740            return null;
741        }
742
743        YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
744        ByteArrayOutputStream out = new ByteArrayOutputStream();
745        yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
746        try {
747            out.close();
748        } catch (Exception e) {
749            Log.e(TAG, "Exception in storing final mosaic", e);
750            return null;
751        }
752        return new MosaicJpeg(out.toByteArray(), width, height);
753    }
754
755    private void setPreviewTexture(SurfaceTexture surface) {
756        try {
757            mCameraDevice.setPreviewTexture(surface);
758        } catch (Throwable ex) {
759            releaseCamera();
760            throw new RuntimeException("setPreviewTexture failed", ex);
761        }
762    }
763
764    private void startCameraPreview() {
765        // If we're previewing already, stop the preview first (this will blank
766        // the screen).
767        if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
768
769        setPreviewTexture(mSurfaceTexture);
770
771        try {
772            Log.v(TAG, "startPreview");
773            mCameraDevice.startPreview();
774        } catch (Throwable ex) {
775            releaseCamera();
776            throw new RuntimeException("startPreview failed", ex);
777        }
778        mCameraState = PREVIEW_ACTIVE;
779    }
780
781    private void stopCameraPreview() {
782        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
783            Log.v(TAG, "stopPreview");
784            mCameraDevice.stopPreview();
785        }
786        mCameraState = PREVIEW_STOPPED;
787    }
788}
789