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