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