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