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