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