PanoramaModule.java revision 9f846cfc5e782a56d218c33b284c1b5b21b7aecc
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;
18
19import android.annotation.TargetApi;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.Configuration;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
27import android.graphics.ImageFormat;
28import android.graphics.PixelFormat;
29import android.graphics.Rect;
30import android.graphics.SurfaceTexture;
31import android.graphics.YuvImage;
32import android.graphics.drawable.Drawable;
33import android.hardware.Camera.Parameters;
34import android.hardware.Camera.Size;
35import android.media.ExifInterface;
36import android.net.Uri;
37import android.os.AsyncTask;
38import android.os.Handler;
39import android.os.Message;
40import android.os.PowerManager;
41import android.util.Log;
42import android.view.KeyEvent;
43import android.view.LayoutInflater;
44import android.view.MotionEvent;
45import android.view.OrientationEventListener;
46import android.view.View;
47import android.view.View.OnClickListener;
48import android.view.ViewGroup;
49import android.view.WindowManager;
50import android.widget.ImageView;
51import android.widget.LinearLayout;
52import android.widget.TextView;
53
54import com.android.camera.CameraManager.CameraProxy;
55import com.android.camera.ui.LayoutChangeNotifier;
56import com.android.camera.ui.LayoutNotifyView;
57import com.android.camera.ui.PopupManager;
58import com.android.camera.ui.Rotatable;
59import com.android.camera.ui.RotateLayout;
60import com.android.gallery3d.common.ApiHelper;
61import com.android.gallery3d.ui.GLRootView;
62
63import java.io.ByteArrayOutputStream;
64import java.io.IOException;
65import java.text.DateFormat;
66import java.text.SimpleDateFormat;
67import java.util.List;
68import java.util.TimeZone;
69
70/**
71 * Activity to handle panorama capturing.
72 */
73@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
74public class PanoramaModule implements CameraModule,
75        SurfaceTexture.OnFrameAvailableListener,
76        ShutterButton.OnShutterButtonListener,
77        LayoutChangeNotifier.Listener {
78
79    public static final int DEFAULT_SWEEP_ANGLE = 160;
80    public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
81    public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
82
83    private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;
84    private static final int MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL = 2;
85    private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 3;
86    private static final int MSG_RESET_TO_PREVIEW = 4;
87    private static final int MSG_CLEAR_SCREEN_DELAY = 5;
88
89    private static final int SCREEN_DELAY = 2 * 60 * 1000;
90
91    private static final String TAG = "CAM PanoModule";
92    private static final int PREVIEW_STOPPED = 0;
93    private static final int PREVIEW_ACTIVE = 1;
94    private static final int CAPTURE_STATE_VIEWFINDER = 0;
95    private static final int CAPTURE_STATE_MOSAIC = 1;
96
97    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
98    private static final String GPS_TIME_FORMAT_STR = "kk/1,mm/1,ss/1";
99    private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
100
101    // Speed is in unit of deg/sec
102    private static final float PANNING_SPEED_THRESHOLD = 25f;
103
104    private ContentResolver mContentResolver;
105
106    private GLRootView mGLRootView;
107    private ViewGroup mPanoLayout;
108    private LinearLayout mCaptureLayout;
109    private View mReviewLayout;
110    private ImageView mReview;
111    private RotateLayout mCaptureIndicator;
112    private PanoProgressBar mPanoProgressBar;
113    private PanoProgressBar mSavingProgressBar;
114    private LayoutNotifyView mPreviewArea;
115    private View mLeftIndicator;
116    private View mRightIndicator;
117    private MosaicPreviewRenderer mMosaicPreviewRenderer;
118    private TextView mTooFastPrompt;
119    private ShutterButton mShutterButton;
120    private Object mWaitObject = new Object();
121
122    private DateFormat mGPSDateStampFormat;
123    private DateFormat mGPSTimeStampFormat;
124    private DateFormat mDateTimeStampFormat;
125
126    private String mPreparePreviewString;
127    private String mDialogTitle;
128    private String mDialogOkString;
129    private String mDialogPanoramaFailedString;
130    private String mDialogWaitingPreviousString;
131
132    private int mIndicatorColor;
133    private int mIndicatorColorFast;
134
135    private boolean mUsingFrontCamera;
136    private int mPreviewWidth;
137    private int mPreviewHeight;
138    private int mCameraState;
139    private int mCaptureState;
140    private PowerManager.WakeLock mPartialWakeLock;
141    private MosaicFrameProcessor mMosaicFrameProcessor;
142    private boolean mMosaicFrameProcessorInitialized;
143    private AsyncTask <Void, Void, Void> mWaitProcessorTask;
144    private long mTimeTaken;
145    private Handler mMainHandler;
146    private SurfaceTexture mCameraTexture;
147    private boolean mThreadRunning;
148    private boolean mCancelComputation;
149    private float mHorizontalViewAngle;
150    private float mVerticalViewAngle;
151
152    // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of
153    // getting a better image quality by the former.
154    private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY;
155
156    private PanoOrientationEventListener mOrientationEventListener;
157    // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise
158    // respectively.
159    private int mDeviceOrientation;
160    private int mDeviceOrientationAtCapture;
161    private int mCameraOrientation;
162    private int mOrientationCompensation;
163
164    private RotateDialogController mRotateDialog;
165
166    private SoundClips.Player mSoundPlayer;
167
168    private Runnable mOnFrameAvailableRunnable;
169
170    private CameraActivity mActivity;
171    private View mRootView;
172    private CameraProxy mCameraDevice;
173    private boolean mOpenCameraFail;
174    private boolean mCameraDisabled;
175    private boolean mPaused;
176
177    private class SetupCameraThread extends Thread {
178        @Override
179        public void run() {
180            try {
181                setupCamera();
182            } catch (CameraHardwareException e) {
183                mOpenCameraFail = true;
184            } catch (CameraDisabledException e) {
185                mCameraDisabled = true;
186            }
187        }
188    }
189
190    private class MosaicJpeg {
191        public MosaicJpeg(byte[] data, int width, int height) {
192            this.data = data;
193            this.width = width;
194            this.height = height;
195            this.isValid = true;
196        }
197
198        public MosaicJpeg() {
199            this.data = null;
200            this.width = 0;
201            this.height = 0;
202            this.isValid = false;
203        }
204
205        public final byte[] data;
206        public final int width;
207        public final int height;
208        public final boolean isValid;
209    }
210
211    private class PanoOrientationEventListener extends OrientationEventListener {
212        public PanoOrientationEventListener(Context context) {
213            super(context);
214        }
215
216        @Override
217        public void onOrientationChanged(int orientation) {
218            // We keep the last known orientation. So if the user first orient
219            // the camera then point the camera to floor or sky, we still have
220            // the correct orientation.
221            if (orientation == ORIENTATION_UNKNOWN) return;
222            mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation);
223            // When the screen is unlocked, display rotation may change. Always
224            // calculate the up-to-date orientationCompensation.
225            int orientationCompensation = mDeviceOrientation
226                    + Util.getDisplayRotation(mActivity) % 360;
227            if (mOrientationCompensation != orientationCompensation) {
228                mOrientationCompensation = orientationCompensation;
229            }
230        }
231    }
232
233    @Override
234    public void init(CameraActivity activity, View parent, boolean reuseScreenNail) {
235        mActivity = activity;
236        mRootView = (ViewGroup) parent;
237
238        createContentView();
239
240        mContentResolver = mActivity.getContentResolver();
241        if (reuseScreenNail) {
242            mActivity.reuseCameraScreenNail(true);
243        } else {
244            mActivity.createCameraScreenNail(true);
245        }
246
247        // This runs in UI thread.
248        mOnFrameAvailableRunnable = new Runnable() {
249            @Override
250            public void run() {
251                // Frames might still be available after the activity is paused.
252                // If we call onFrameAvailable after pausing, the GL thread will crash.
253                if (mPaused) return;
254
255                if (mGLRootView.getVisibility() != View.VISIBLE) {
256                    mMosaicPreviewRenderer.showPreviewFrameSync();
257                    mGLRootView.setVisibility(View.VISIBLE);
258                } else {
259                    if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {
260                        mMosaicPreviewRenderer.showPreviewFrame();
261                    } else {
262                        mMosaicPreviewRenderer.alignFrame();
263                        mMosaicFrameProcessor.processFrame();
264                    }
265                }
266            }
267        };
268
269        mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
270        mGPSTimeStampFormat = new SimpleDateFormat(GPS_TIME_FORMAT_STR);
271        mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
272        TimeZone tzUTC = TimeZone.getTimeZone("UTC");
273        mGPSDateStampFormat.setTimeZone(tzUTC);
274        mGPSTimeStampFormat.setTimeZone(tzUTC);
275
276        PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
277        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama");
278
279        mOrientationEventListener = new PanoOrientationEventListener(mActivity);
280
281        mMosaicFrameProcessor = MosaicFrameProcessor.getInstance();
282
283        Resources appRes = mActivity.getResources();
284        mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview);
285        mDialogTitle = appRes.getString(R.string.pano_dialog_title);
286        mDialogOkString = appRes.getString(R.string.dialog_ok);
287        mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed);
288        mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous);
289
290        mGLRootView = (GLRootView) mActivity.getGLRoot();
291
292        mMainHandler = new Handler() {
293            @Override
294            public void handleMessage(Message msg) {
295                switch (msg.what) {
296                    case MSG_LOW_RES_FINAL_MOSAIC_READY:
297                        onBackgroundThreadFinished();
298                        showFinalMosaic((Bitmap) msg.obj);
299                        saveHighResMosaic();
300                        break;
301                    case MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL:
302                        onBackgroundThreadFinished();
303                        // If the activity is paused, save the thumbnail to the file here.
304                        // If not, it will be saved in onPause.
305                        if (mPaused) mActivity.saveThumbnailToFile();
306                        // Set the thumbnail bitmap here because mThumbnailView must be accessed
307                        // from the UI thread.
308
309                        resetToPreview();
310                        clearMosaicFrameProcessorIfNeeded();
311                        break;
312                    case MSG_GENERATE_FINAL_MOSAIC_ERROR:
313                        onBackgroundThreadFinished();
314                        if (mPaused) {
315                            resetToPreview();
316                        } else {
317                            mRotateDialog.showAlertDialog(
318                                    mDialogTitle, mDialogPanoramaFailedString,
319                                    mDialogOkString, new Runnable() {
320                                        @Override
321                                        public void run() {
322                                            resetToPreview();
323                                        }},
324                                    null, null);
325                        }
326                        clearMosaicFrameProcessorIfNeeded();
327                        break;
328                    case MSG_RESET_TO_PREVIEW:
329                        onBackgroundThreadFinished();
330                        resetToPreview();
331                        clearMosaicFrameProcessorIfNeeded();
332                        break;
333                    case MSG_CLEAR_SCREEN_DELAY:
334                        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.
335                                FLAG_KEEP_SCREEN_ON);
336                        break;
337                }
338            }
339        };
340    }
341
342    @Override
343    public boolean dispatchTouchEvent(MotionEvent m) {
344        // Dismiss the mode selection window if the ACTION_DOWN event is out of
345        // its view area.
346        return false;
347    }
348
349    private void setupCamera() throws CameraHardwareException, CameraDisabledException {
350        openCamera();
351        Parameters parameters = mCameraDevice.getParameters();
352        setupCaptureParams(parameters);
353        configureCamera(parameters);
354    }
355
356    private void releaseCamera() {
357        if (mCameraDevice != null) {
358            mCameraDevice.setPreviewCallbackWithBuffer(null);
359            CameraHolder.instance().release();
360            mCameraDevice = null;
361            mCameraState = PREVIEW_STOPPED;
362        }
363    }
364
365    private void openCamera() throws CameraHardwareException, CameraDisabledException {
366        int cameraId = CameraHolder.instance().getBackCameraId();
367        // If there is no back camera, use the first camera. Camera id starts
368        // from 0. Currently if a camera is not back facing, it is front facing.
369        // This is also forward compatible if we have a new facing other than
370        // back or front in the future.
371        if (cameraId == -1) cameraId = 0;
372        mCameraDevice = Util.openCamera(mActivity, cameraId);
373        mCameraOrientation = Util.getCameraOrientation(cameraId);
374        if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true;
375    }
376
377    private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
378            boolean needSmaller) {
379        int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
380        boolean hasFound = false;
381        for (Size size : supportedSizes) {
382            int h = size.height;
383            int w = size.width;
384            // we only want 4:3 format.
385            int d = DEFAULT_CAPTURE_PIXELS - h * w;
386            if (needSmaller && d < 0) { // no bigger preview than 960x720.
387                continue;
388            }
389            if (need4To3 && (h * 4 != w * 3)) {
390                continue;
391            }
392            d = Math.abs(d);
393            if (d < pixelsDiff) {
394                mPreviewWidth = w;
395                mPreviewHeight = h;
396                pixelsDiff = d;
397                hasFound = true;
398            }
399        }
400        return hasFound;
401    }
402
403    private void setupCaptureParams(Parameters parameters) {
404        List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
405        if (!findBestPreviewSize(supportedSizes, true, true)) {
406            Log.w(TAG, "No 4:3 ratio preview size supported.");
407            if (!findBestPreviewSize(supportedSizes, false, true)) {
408                Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
409                findBestPreviewSize(supportedSizes, false, false);
410            }
411        }
412        Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth);
413        parameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
414
415        List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
416        int last = frameRates.size() - 1;
417        int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
418        int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
419        parameters.setPreviewFpsRange(minFps, maxFps);
420        Log.v(TAG, "preview fps: " + minFps + ", " + maxFps);
421
422        List<String> supportedFocusModes = parameters.getSupportedFocusModes();
423        if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) {
424            parameters.setFocusMode(mTargetFocusMode);
425        } else {
426            // Use the default focus mode and log a message
427            Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode +
428                  " becuase the mode is not supported.");
429        }
430
431        parameters.set(Util.RECORDING_HINT, Util.FALSE);
432
433        mHorizontalViewAngle = parameters.getHorizontalViewAngle();
434        mVerticalViewAngle =  parameters.getVerticalViewAngle();
435    }
436
437    public int getPreviewBufSize() {
438        PixelFormat pixelInfo = new PixelFormat();
439        PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
440        // TODO: remove this extra 32 byte after the driver bug is fixed.
441        return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
442    }
443
444    private void configureCamera(Parameters parameters) {
445        mCameraDevice.setParameters(parameters);
446    }
447
448    private void configMosaicPreview(int w, int h) {
449        stopCameraPreview();
450        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
451        screenNail.setSize(w, h);
452        if (screenNail.getSurfaceTexture() == null) {
453            screenNail.acquireSurfaceTexture();
454        } else {
455            screenNail.releaseSurfaceTexture();
456            screenNail.acquireSurfaceTexture();
457            mActivity.notifyScreenNailChanged();
458        }
459        boolean isLandscape = (mActivity.getResources().getConfiguration().orientation
460                == Configuration.ORIENTATION_LANDSCAPE);
461        if (mMosaicPreviewRenderer != null) mMosaicPreviewRenderer.release();
462        mMosaicPreviewRenderer = new MosaicPreviewRenderer(
463                screenNail.getSurfaceTexture(), w, h, isLandscape);
464
465        mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture();
466        if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) {
467            resetToPreview();
468        }
469    }
470
471    // Receives the layout change event from the preview area. So we can set
472    // the camera preview screennail to the same size and initialize the mosaic
473    // preview renderer.
474    @Override
475    public void onLayoutChange(View v, int l, int t, int r, int b) {
476        Log.i(TAG, "layout change: "+(r - l) + "/" +(b - t));
477        mActivity.onLayoutChange(v, l, t, r, b);
478        configMosaicPreview(r - l, b - t);
479    }
480
481    @Override
482    public void onFrameAvailable(SurfaceTexture surface) {
483        /* This function may be called by some random thread,
484         * so let's be safe and jump back to ui thread.
485         * No OpenGL calls can be done here. */
486        mActivity.runOnUiThread(mOnFrameAvailableRunnable);
487    }
488
489    private void hideDirectionIndicators() {
490        mLeftIndicator.setVisibility(View.GONE);
491        mRightIndicator.setVisibility(View.GONE);
492    }
493
494    private void showDirectionIndicators(int direction) {
495        switch (direction) {
496            case PanoProgressBar.DIRECTION_NONE:
497                mLeftIndicator.setVisibility(View.VISIBLE);
498                mRightIndicator.setVisibility(View.VISIBLE);
499                break;
500            case PanoProgressBar.DIRECTION_LEFT:
501                mLeftIndicator.setVisibility(View.VISIBLE);
502                mRightIndicator.setVisibility(View.GONE);
503                break;
504            case PanoProgressBar.DIRECTION_RIGHT:
505                mLeftIndicator.setVisibility(View.GONE);
506                mRightIndicator.setVisibility(View.VISIBLE);
507                break;
508        }
509    }
510
511    public void startCapture() {
512        // Reset values so we can do this again.
513        mCancelComputation = false;
514        mTimeTaken = System.currentTimeMillis();
515        mActivity.setSwipingEnabled(false);
516        mActivity.hideSwitcher();
517        mCaptureState = CAPTURE_STATE_MOSAIC;
518        mShutterButton.setActivated(true);
519        mCaptureIndicator.setVisibility(View.VISIBLE);
520        showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);
521
522        mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
523            @Override
524            public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
525                    float progressX, float progressY) {
526                float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
527                float accumulatedVerticalAngle = progressY * mVerticalViewAngle;
528                if (isFinished
529                        || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
530                        || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {
531                    stopCapture(false);
532                } else {
533                    float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
534                    float panningRateYInDegree = panningRateY * mVerticalViewAngle;
535                    updateProgress(panningRateXInDegree, panningRateYInDegree,
536                            accumulatedHorizontalAngle, accumulatedVerticalAngle);
537                }
538            }
539        });
540
541        mPanoProgressBar.reset();
542        // TODO: calculate the indicator width according to different devices to reflect the actual
543        // angle of view of the camera device.
544        mPanoProgressBar.setIndicatorWidth(20);
545        mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE);
546        mPanoProgressBar.setVisibility(View.VISIBLE);
547        mDeviceOrientationAtCapture = mDeviceOrientation;
548        keepScreenOn();
549    }
550
551    private void stopCapture(boolean aborted) {
552        mCaptureState = CAPTURE_STATE_VIEWFINDER;
553        mCaptureIndicator.setVisibility(View.GONE);
554        hideTooFastIndication();
555        hideDirectionIndicators();
556
557        mMosaicFrameProcessor.setProgressListener(null);
558        stopCameraPreview();
559
560        mCameraTexture.setOnFrameAvailableListener(null);
561
562        if (!aborted && !mThreadRunning) {
563            mRotateDialog.showWaitingDialog(mPreparePreviewString);
564            runBackgroundThread(new Thread() {
565                @Override
566                public void run() {
567                    MosaicJpeg jpeg = generateFinalMosaic(false);
568
569                    if (jpeg != null && jpeg.isValid) {
570                        Bitmap bitmap = null;
571                        bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
572                        mMainHandler.sendMessage(mMainHandler.obtainMessage(
573                                MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));
574                    } else {
575                        mMainHandler.sendMessage(mMainHandler.obtainMessage(
576                                MSG_RESET_TO_PREVIEW));
577                    }
578                }
579            });
580        }
581        keepScreenOnAwhile();
582    }
583
584    private void showTooFastIndication() {
585        mTooFastPrompt.setVisibility(View.VISIBLE);
586        // The PreviewArea also contains the border for "too fast" indication.
587        mPreviewArea.setVisibility(View.VISIBLE);
588        mPanoProgressBar.setIndicatorColor(mIndicatorColorFast);
589        mLeftIndicator.setEnabled(true);
590        mRightIndicator.setEnabled(true);
591    }
592
593    private void hideTooFastIndication() {
594        mTooFastPrompt.setVisibility(View.GONE);
595        // We set "INVISIBLE" instead of "GONE" here because we need mPreviewArea to have layout
596        // information so we can know the size and position for mCameraScreenNail.
597        mPreviewArea.setVisibility(View.INVISIBLE);
598        mPanoProgressBar.setIndicatorColor(mIndicatorColor);
599        mLeftIndicator.setEnabled(false);
600        mRightIndicator.setEnabled(false);
601    }
602
603    private void updateProgress(float panningRateXInDegree, float panningRateYInDegree,
604            float progressHorizontalAngle, float progressVerticalAngle) {
605        mGLRootView.requestRender();
606
607        // TODO: Now we just display warning message by the panning speed.
608        // Since we only support horizontal panning, we should display a warning message
609        // in UI when there're significant vertical movements.
610        if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD)
611            || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) {
612            showTooFastIndication();
613        } else {
614            hideTooFastIndication();
615        }
616        int angleInMajorDirection =
617                (Math.abs(progressHorizontalAngle) > Math.abs(progressVerticalAngle))
618                ? (int) progressHorizontalAngle
619                : (int) progressVerticalAngle;
620        mPanoProgressBar.setProgress((angleInMajorDirection));
621    }
622
623    private void setViews(Resources appRes) {
624        mCaptureState = CAPTURE_STATE_VIEWFINDER;
625        mPanoProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar);
626        mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
627        mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));
628        mPanoProgressBar.setIndicatorColor(mIndicatorColor);
629        mPanoProgressBar.setOnDirectionChangeListener(
630                new PanoProgressBar.OnDirectionChangeListener () {
631                    @Override
632                    public void onDirectionChange(int direction) {
633                        if (mCaptureState == CAPTURE_STATE_MOSAIC) {
634                            showDirectionIndicators(direction);
635                        }
636                    }
637                });
638
639        mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator);
640        mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator);
641        mLeftIndicator.setEnabled(false);
642        mRightIndicator.setEnabled(false);
643        mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview);
644        // This mPreviewArea also shows the border for visual "too fast" indication.
645        mPreviewArea = (LayoutNotifyView) mRootView.findViewById(R.id.pano_preview_area);
646        mPreviewArea.setOnLayoutChangeListener(this);
647
648        mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar);
649        mSavingProgressBar.setIndicatorWidth(0);
650        mSavingProgressBar.setMaxProgress(100);
651        mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
652        mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication));
653
654        mCaptureIndicator = (RotateLayout) mRootView.findViewById(R.id.pano_capture_indicator);
655
656        mReviewLayout = mRootView.findViewById(R.id.pano_review_layout);
657        mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea);
658        View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button);
659        cancelButton.setOnClickListener(new OnClickListener() {
660            @Override
661            public void onClick(View arg0) {
662                if (mPaused || mCameraTexture == null) return;
663                cancelHighResComputation();
664            }
665        });
666
667        mShutterButton = mActivity.getShutterButton();
668        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
669        mShutterButton.setOnShutterButtonListener(this);
670
671        if (mActivity.getResources().getConfiguration().orientation
672                == Configuration.ORIENTATION_PORTRAIT) {
673            Rotatable[] rotateLayout = {
674                    (Rotatable) mRootView.findViewById(R.id.pano_pan_progress_bar_layout),
675                    (Rotatable) mRootView.findViewById(R.id.pano_capture_too_fast_textview_layout),
676                    (Rotatable) mRootView.findViewById(R.id.pano_review_saving_indication_layout),
677                    (Rotatable) mRootView.findViewById(R.id.pano_saving_progress_bar_layout),
678                    (Rotatable) mRootView.findViewById(R.id.pano_review_cancel_button_layout),
679                    (Rotatable) mRootView.findViewById(R.id.pano_rotate_reviewarea),
680                    mRotateDialog,
681                    mCaptureIndicator,
682                    };
683            for (Rotatable r : rotateLayout) {
684                r.setOrientation(270, false);
685            }
686        } else {
687            // Even if the orientation is 0, we still need to set because it might be previously
688            // set when the configuration is portrait.
689            mRotateDialog.setOrientation(0, false);
690        }
691    }
692
693    private void createContentView() {
694        mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView);
695        Resources appRes = mActivity.getResources();
696        mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app_root);
697        mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
698        mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
699        mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.pano_layout);
700        mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
701        setViews(appRes);
702    }
703
704    @Override
705    public void onShutterButtonClick() {
706        // If mCameraTexture == null then GL setup is not finished yet.
707        // No buttons can be pressed.
708        if (mPaused || mThreadRunning || mCameraTexture == null) return;
709        // Since this button will stay on the screen when capturing, we need to check the state
710        // right now.
711        switch (mCaptureState) {
712            case CAPTURE_STATE_VIEWFINDER:
713                if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return;
714                mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
715                startCapture();
716                break;
717            case CAPTURE_STATE_MOSAIC:
718                mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
719                stopCapture(false);
720        }
721    }
722
723    @Override
724    public void onShutterButtonFocus(boolean pressed) {
725    }
726
727    public void reportProgress() {
728        mSavingProgressBar.reset();
729        mSavingProgressBar.setRightIncreasing(true);
730        Thread t = new Thread() {
731            @Override
732            public void run() {
733                while (mThreadRunning) {
734                    final int progress = mMosaicFrameProcessor.reportProgress(
735                            true, mCancelComputation);
736
737                    try {
738                        synchronized (mWaitObject) {
739                            mWaitObject.wait(50);
740                        }
741                    } catch (InterruptedException e) {
742                        throw new RuntimeException("Panorama reportProgress failed", e);
743                    }
744                    // Update the progress bar
745                    mActivity.runOnUiThread(new Runnable() {
746                        @Override
747                        public void run() {
748                            mSavingProgressBar.setProgress(progress);
749                        }
750                    });
751                }
752            }
753        };
754        t.start();
755    }
756
757    public void saveHighResMosaic() {
758        runBackgroundThread(new Thread() {
759            @Override
760            public void run() {
761                mPartialWakeLock.acquire();
762                MosaicJpeg jpeg;
763                try {
764                    jpeg = generateFinalMosaic(true);
765                } finally {
766                    mPartialWakeLock.release();
767                }
768
769                if (jpeg == null) {  // Cancelled by user.
770                    mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);
771                } else if (!jpeg.isValid) {  // Error when generating mosaic.
772                    mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
773                } else {
774                    // The panorama image returned from the library is oriented based on the
775                    // natural orientation of a camera. We need to set an orientation for the image
776                    // in its EXIF header, so the image can be displayed correctly.
777                    // The orientation is calculated from compensating the
778                    // device orientation at capture and the camera orientation respective to
779                    // the natural orientation of the device.
780                    int orientation;
781                    if (mUsingFrontCamera) {
782                        // mCameraOrientation is negative with respect to the front facing camera.
783                        // See document of android.hardware.Camera.Parameters.setRotation.
784                        orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360;
785                    } else {
786                        orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360;
787                    }
788                    Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation);
789                    if (uri != null) {
790                        mActivity.addSecureAlbumItemIfNeeded(false, uri);
791                        // Create a thumbnail whose width and height is equal or bigger
792                        // than the thumbnail view's width.
793                        int ratio = (int) Math.ceil(
794                                (double) (jpeg.height > jpeg.width ? jpeg.width : jpeg.height)
795                                / mActivity.mThumbnailViewWidth);
796                        int inSampleSize = Integer.highestOneBit(ratio);
797                        mActivity.mThumbnail = Thumbnail.createThumbnail(
798                                jpeg.data, orientation, inSampleSize, uri);
799                        Util.broadcastNewPicture(mActivity, uri);
800                    }
801                    mMainHandler.sendMessage(
802                            mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL));
803                }
804            }
805        });
806        reportProgress();
807    }
808
809    private void runBackgroundThread(Thread thread) {
810        mThreadRunning = true;
811        thread.start();
812    }
813
814    private void onBackgroundThreadFinished() {
815        mThreadRunning = false;
816        mRotateDialog.dismissDialog();
817    }
818
819    private void cancelHighResComputation() {
820        mCancelComputation = true;
821        synchronized (mWaitObject) {
822            mWaitObject.notify();
823        }
824    }
825
826    @OnClickAttr
827    public void onThumbnailClicked(View v) {
828        if (mPaused || mThreadRunning || mCameraTexture == null
829                || mActivity.mThumbnail == null) return;
830        mActivity.gotoGallery();
831    }
832
833    // This function will be called upon the first camera frame is available.
834    private void reset() {
835        mCaptureState = CAPTURE_STATE_VIEWFINDER;
836
837        // We should set mGLRootView visible too. However, since there might be no
838        // frame available yet, setting mGLRootView visible should be done right after
839        // the first camera frame is available and therefore it is done by
840        // mOnFirstFrameAvailableRunnable.
841        mActivity.setSwipingEnabled(true);
842        mActivity.showSwitcher();
843        mReviewLayout.setVisibility(View.GONE);
844        mShutterButton.setActivated(false);
845        mPanoProgressBar.setVisibility(View.GONE);
846        mCaptureLayout.setVisibility(View.VISIBLE);
847        mMosaicFrameProcessor.reset();
848    }
849
850    private void resetToPreview() {
851        reset();
852        if (!mPaused) startCameraPreview();
853    }
854
855    private void showFinalMosaic(Bitmap bitmap) {
856        if (bitmap != null) {
857            mReview.setImageBitmap(bitmap);
858        }
859
860        mGLRootView.setVisibility(View.GONE);
861        mCaptureLayout.setVisibility(View.GONE);
862        mReviewLayout.setVisibility(View.VISIBLE);
863    }
864
865    private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
866        if (jpegData != null) {
867            String filename = PanoUtil.createName(
868                    mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
869            Uri uri = Storage.addImage(mContentResolver, filename, mTimeTaken, null,
870                    orientation, jpegData, width, height);
871            if (uri != null) {
872                String filepath = Storage.generateFilepath(filename);
873                try {
874                    ExifInterface exif = new ExifInterface(filepath);
875
876                    exif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP,
877                            mGPSDateStampFormat.format(mTimeTaken));
878                    exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP,
879                            mGPSTimeStampFormat.format(mTimeTaken));
880                    exif.setAttribute(ExifInterface.TAG_DATETIME,
881                            mDateTimeStampFormat.format(mTimeTaken));
882                    exif.setAttribute(ExifInterface.TAG_ORIENTATION,
883                            getExifOrientation(orientation));
884
885                    exif.saveAttributes();
886                } catch (IOException e) {
887                    Log.e(TAG, "Cannot set EXIF for " + filepath, e);
888                }
889            }
890            return uri;
891        }
892        return null;
893    }
894
895    private static String getExifOrientation(int orientation) {
896        switch (orientation) {
897            case 0:
898                return String.valueOf(ExifInterface.ORIENTATION_NORMAL);
899            case 90:
900                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
901            case 180:
902                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
903            case 270:
904                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
905            default:
906                throw new AssertionError("invalid: " + orientation);
907        }
908    }
909
910    private void clearMosaicFrameProcessorIfNeeded() {
911        if (!mPaused || mThreadRunning) return;
912        // Only clear the processor if it is initialized by this activity
913        // instance. Other activity instances may be using it.
914        if (mMosaicFrameProcessorInitialized) {
915            mMosaicFrameProcessor.clear();
916            mMosaicFrameProcessorInitialized = false;
917        }
918    }
919
920    private void initMosaicFrameProcessorIfNeeded() {
921        if (mPaused || mThreadRunning) return;
922        mMosaicFrameProcessor.initialize(
923                mPreviewWidth, mPreviewHeight, getPreviewBufSize());
924        mMosaicFrameProcessorInitialized = true;
925    }
926
927    @Override
928    public void onPauseBeforeSuper() {
929        mPaused = true;
930    }
931
932    @Override
933    public void onPauseAfterSuper() {
934        mOrientationEventListener.disable();
935        if (mCameraDevice == null) {
936            // Camera open failed. Nothing should be done here.
937            return;
938        }
939        // Stop the capturing first.
940        if (mCaptureState == CAPTURE_STATE_MOSAIC) {
941            stopCapture(true);
942            reset();
943        }
944
945        releaseCamera();
946        mCameraTexture = null;
947
948        // The preview renderer might not have a chance to be initialized before
949        // onPause().
950        if (mMosaicPreviewRenderer != null) {
951            mMosaicPreviewRenderer.release();
952            mMosaicPreviewRenderer = null;
953        }
954
955        clearMosaicFrameProcessorIfNeeded();
956        if (mWaitProcessorTask != null) {
957            mWaitProcessorTask.cancel(true);
958            mWaitProcessorTask = null;
959        }
960        resetScreenOn();
961        if (mSoundPlayer != null) {
962            mSoundPlayer.release();
963            mSoundPlayer = null;
964        }
965        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
966        if (screenNail.getSurfaceTexture() != null) {
967            screenNail.releaseSurfaceTexture();
968        }
969        System.gc();
970    }
971
972    @Override
973    public void onConfigurationChanged(Configuration newConfig) {
974
975        Drawable lowResReview = null;
976        if (mThreadRunning) lowResReview = mReview.getDrawable();
977
978        // Change layout in response to configuration change
979        mCaptureLayout.setOrientation(
980                newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
981                ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
982        mCaptureLayout.removeAllViews();
983        LayoutInflater inflater = mActivity.getLayoutInflater();
984        inflater.inflate(R.layout.preview_frame_pano, mCaptureLayout);
985
986        mPanoLayout.removeView(mReviewLayout);
987        inflater.inflate(R.layout.pano_review, mPanoLayout);
988
989        setViews(mActivity.getResources());
990        if (mThreadRunning) {
991            mReview.setImageDrawable(lowResReview);
992            mCaptureLayout.setVisibility(View.GONE);
993            mReviewLayout.setVisibility(View.VISIBLE);
994        }
995
996        mActivity.updateThumbnailView();
997    }
998
999    @Override
1000    public void onResumeBeforeSuper() {
1001        mPaused = false;
1002    }
1003
1004    @Override
1005    public void onResumeAfterSuper() {
1006        mOrientationEventListener.enable();
1007
1008        mCaptureState = CAPTURE_STATE_VIEWFINDER;
1009
1010        SetupCameraThread setupCameraThread = new SetupCameraThread();
1011        setupCameraThread.start();
1012        try {
1013            setupCameraThread.join();
1014        } catch (InterruptedException ex) {
1015            // ignore
1016        }
1017
1018        if (mOpenCameraFail) {
1019            Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
1020            return;
1021        } else if (mCameraDisabled) {
1022            Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
1023            return;
1024        }
1025
1026        // Set up sound playback for shutter button
1027        mSoundPlayer = SoundClips.getPlayer(mActivity);
1028
1029        // Check if another panorama instance is using the mosaic frame processor.
1030        mRotateDialog.dismissDialog();
1031        if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
1032            mGLRootView.setVisibility(View.GONE);
1033            mRotateDialog.showWaitingDialog(mDialogWaitingPreviousString);
1034            mWaitProcessorTask = new WaitProcessorTask().execute();
1035        } else {
1036            if (!mThreadRunning) mGLRootView.setVisibility(View.VISIBLE);
1037            // Camera must be initialized before MosaicFrameProcessor is
1038            // initialized. The preview size has to be decided by camera device.
1039            initMosaicFrameProcessorIfNeeded();
1040            int w = mPreviewArea.getWidth();
1041            int h = mPreviewArea.getHeight();
1042            if (w != 0 && h != 0) {  // The layout has been calculated.
1043                configMosaicPreview(w, h);
1044            }
1045        }
1046        mActivity.getLastThumbnail();
1047        keepScreenOnAwhile();
1048
1049        // Dismiss open menu if exists.
1050        PopupManager.getInstance(mActivity).notifyShowPopup(null);
1051        mRootView.requestLayout();
1052    }
1053
1054    /**
1055     * Generate the final mosaic image.
1056     *
1057     * @param highRes flag to indicate whether we want to get a high-res version.
1058     * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
1059     *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
1060     *         is an error in generating the final mosaic.
1061     */
1062    public MosaicJpeg generateFinalMosaic(boolean highRes) {
1063        int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
1064        if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
1065            return null;
1066        } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
1067            return new MosaicJpeg();
1068        }
1069
1070        byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
1071        if (imageData == null) {
1072            Log.e(TAG, "getFinalMosaicNV21() returned null.");
1073            return new MosaicJpeg();
1074        }
1075
1076        int len = imageData.length - 8;
1077        int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
1078                + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
1079        int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
1080                + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
1081        Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
1082
1083        if (width <= 0 || height <= 0) {
1084            // TODO: pop up an error message indicating that the final result is not generated.
1085            Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
1086                    height);
1087            return new MosaicJpeg();
1088        }
1089
1090        YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
1091        ByteArrayOutputStream out = new ByteArrayOutputStream();
1092        yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
1093        try {
1094            out.close();
1095        } catch (Exception e) {
1096            Log.e(TAG, "Exception in storing final mosaic", e);
1097            return new MosaicJpeg();
1098        }
1099        return new MosaicJpeg(out.toByteArray(), width, height);
1100    }
1101
1102    private void startCameraPreview() {
1103        if (mCameraDevice == null) {
1104            // Camera open failed. Return.
1105            return;
1106        }
1107
1108        // This works around a driver issue. startPreview may fail if
1109        // stopPreview/setPreviewTexture/startPreview are called several times
1110        // in a row. mCameraTexture can be null after pressing home during
1111        // mosaic generation and coming back. Preview will be started later in
1112        // onLayoutChange->configMosaicPreview. This also reduces the latency.
1113        if (mCameraTexture == null) return;
1114
1115        // If we're previewing already, stop the preview first (this will blank
1116        // the screen).
1117        if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
1118
1119        // Set the display orientation to 0, so that the underlying mosaic library
1120        // can always get undistorted mPreviewWidth x mPreviewHeight image data
1121        // from SurfaceTexture.
1122        mCameraDevice.setDisplayOrientation(0);
1123
1124        if (mCameraTexture != null) mCameraTexture.setOnFrameAvailableListener(this);
1125        mCameraDevice.setPreviewTextureAsync(mCameraTexture);
1126
1127        mCameraDevice.startPreviewAsync();
1128        mCameraState = PREVIEW_ACTIVE;
1129    }
1130
1131    private void stopCameraPreview() {
1132        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1133            Log.v(TAG, "stopPreview");
1134            mCameraDevice.stopPreview();
1135        }
1136        mCameraState = PREVIEW_STOPPED;
1137    }
1138
1139    @Override
1140    public void onUserInteraction() {
1141        if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile();
1142    }
1143
1144    @Override
1145    public boolean onBackPressed() {
1146        // If panorama is generating low res or high res mosaic, ignore back
1147        // key. So the activity will not be destroyed.
1148        if (mThreadRunning) return true;
1149        return false;
1150    }
1151
1152    private void resetScreenOn() {
1153        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
1154        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1155    }
1156
1157    private void keepScreenOnAwhile() {
1158        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
1159        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1160        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1161    }
1162
1163    private void keepScreenOn() {
1164        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
1165        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1166    }
1167
1168    private class WaitProcessorTask extends AsyncTask<Void, Void, Void> {
1169        @Override
1170        protected Void doInBackground(Void... params) {
1171            synchronized (mMosaicFrameProcessor) {
1172                while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
1173                    try {
1174                        mMosaicFrameProcessor.wait();
1175                    } catch (Exception e) {
1176                        // ignore
1177                    }
1178                }
1179            }
1180            return null;
1181        }
1182
1183        @Override
1184        protected void onPostExecute(Void result) {
1185            mWaitProcessorTask = null;
1186            mRotateDialog.dismissDialog();
1187            mGLRootView.setVisibility(View.VISIBLE);
1188            initMosaicFrameProcessorIfNeeded();
1189            int w = mPreviewArea.getWidth();
1190            int h = mPreviewArea.getHeight();
1191            if (w != 0 && h != 0) {  // The layout has been calculated.
1192                configMosaicPreview(w, h);
1193            }
1194            resetToPreview();
1195        }
1196    }
1197
1198    @Override
1199    public void onFullScreenChanged(boolean full) {
1200    }
1201
1202
1203    @Override
1204    public void onStop() {
1205    }
1206
1207    @Override
1208    public void installIntentFilter() {
1209    }
1210
1211    @Override
1212    public void onActivityResult(int requestCode, int resultCode, Intent data) {
1213    }
1214
1215
1216    @Override
1217    public boolean onKeyDown(int keyCode, KeyEvent event) {
1218        return false;
1219    }
1220
1221    @Override
1222    public boolean onKeyUp(int keyCode, KeyEvent event) {
1223        return false;
1224    }
1225
1226    @Override
1227    public void onSingleTapUp(View view, int x, int y) {
1228    }
1229
1230    @Override
1231    public void onPreviewTextureCopied() {
1232    }
1233
1234    @Override
1235    public boolean updateStorageHintOnResume() {
1236        return false;
1237    }
1238
1239    @Override
1240    public void updateCameraAppView() {
1241    }
1242
1243    @Override
1244    public boolean collapseCameraControls() {
1245        return false;
1246    }
1247}
1248