Home | History | Annotate | Download | only in camera
      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 
     17 package com.android.camera;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.res.Configuration;
     24 import android.content.res.Resources;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.graphics.Canvas;
     28 import android.graphics.ImageFormat;
     29 import android.graphics.Matrix;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.Rect;
     32 import android.graphics.SurfaceTexture;
     33 import android.graphics.YuvImage;
     34 import android.graphics.drawable.BitmapDrawable;
     35 import android.graphics.drawable.Drawable;
     36 import android.hardware.Camera.Parameters;
     37 import android.hardware.Camera.Size;
     38 import android.net.Uri;
     39 import android.os.AsyncTask;
     40 import android.os.Handler;
     41 import android.os.Message;
     42 import android.os.PowerManager;
     43 import android.util.Log;
     44 import android.view.KeyEvent;
     45 import android.view.LayoutInflater;
     46 import android.view.MotionEvent;
     47 import android.view.OrientationEventListener;
     48 import android.view.View;
     49 import android.view.View.OnClickListener;
     50 import android.view.ViewGroup;
     51 import android.view.WindowManager;
     52 import android.widget.ImageView;
     53 import android.widget.LinearLayout;
     54 import android.widget.TextView;
     55 
     56 import com.android.camera.CameraManager.CameraProxy;
     57 import com.android.camera.ui.LayoutChangeNotifier;
     58 import com.android.camera.ui.LayoutNotifyView;
     59 import com.android.camera.ui.PopupManager;
     60 import com.android.camera.ui.Rotatable;
     61 import com.android.gallery3d.common.ApiHelper;
     62 import com.android.gallery3d.exif.ExifData;
     63 import com.android.gallery3d.exif.ExifInvalidFormatException;
     64 import com.android.gallery3d.exif.ExifOutputStream;
     65 import com.android.gallery3d.exif.ExifReader;
     66 import com.android.gallery3d.exif.ExifTag;
     67 import com.android.gallery3d.ui.GLRootView;
     68 
     69 import java.io.ByteArrayInputStream;
     70 import java.io.ByteArrayOutputStream;
     71 import java.io.File;
     72 import java.io.FileOutputStream;
     73 import java.io.IOException;
     74 import java.io.InputStream;
     75 import java.util.List;
     76 import java.util.TimeZone;
     77 
     78 /**
     79  * Activity to handle panorama capturing.
     80  */
     81 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
     82 public 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