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