Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2013 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.content.ContentResolver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.graphics.Bitmap;
     25 import android.graphics.BitmapFactory;
     26 import android.graphics.ImageFormat;
     27 import android.graphics.PixelFormat;
     28 import android.graphics.Point;
     29 import android.graphics.Rect;
     30 import android.graphics.SurfaceTexture;
     31 import android.graphics.YuvImage;
     32 import android.hardware.Camera.Parameters;
     33 import android.hardware.Camera.Size;
     34 import android.location.Location;
     35 import android.net.Uri;
     36 import android.os.AsyncTask;
     37 import android.os.Handler;
     38 import android.os.Message;
     39 import android.os.PowerManager;
     40 import android.util.Log;
     41 import android.view.KeyEvent;
     42 import android.view.OrientationEventListener;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 import android.view.WindowManager;
     46 
     47 import com.android.camera.CameraManager.CameraProxy;
     48 import com.android.camera.app.OrientationManager;
     49 import com.android.camera.data.LocalData;
     50 import com.android.camera.exif.ExifInterface;
     51 import com.android.camera.util.CameraUtil;
     52 import com.android.camera.util.UsageStatistics;
     53 import com.android.camera2.R;
     54 
     55 import java.io.ByteArrayOutputStream;
     56 import java.io.File;
     57 import java.io.IOException;
     58 import java.util.List;
     59 import java.util.TimeZone;
     60 
     61 /**
     62  * Activity to handle panorama capturing.
     63  */
     64 public class WideAnglePanoramaModule
     65         implements CameraModule, WideAnglePanoramaController,
     66         SurfaceTexture.OnFrameAvailableListener {
     67 
     68     public static final int DEFAULT_SWEEP_ANGLE = 160;
     69     public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
     70     public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
     71 
     72     private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;
     73     private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2;
     74     private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3;
     75     private static final int MSG_CLEAR_SCREEN_DELAY = 4;
     76     private static final int MSG_RESET_TO_PREVIEW = 5;
     77 
     78     private static final int SCREEN_DELAY = 2 * 60 * 1000;
     79 
     80     @SuppressWarnings("unused")
     81     private static final String TAG = "CAM_WidePanoModule";
     82     private static final int PREVIEW_STOPPED = 0;
     83     private static final int PREVIEW_ACTIVE = 1;
     84     public static final int CAPTURE_STATE_VIEWFINDER = 0;
     85     public static final int CAPTURE_STATE_MOSAIC = 1;
     86 
     87     // The unit of speed is degrees per frame.
     88     private static final float PANNING_SPEED_THRESHOLD = 2.5f;
     89     private static final boolean DEBUG = false;
     90 
     91     private ContentResolver mContentResolver;
     92     private WideAnglePanoramaUI mUI;
     93 
     94     private MosaicPreviewRenderer mMosaicPreviewRenderer;
     95     private Object mRendererLock = new Object();
     96     private Object mWaitObject = new Object();
     97 
     98     private String mPreparePreviewString;
     99     private String mDialogTitle;
    100     private String mDialogOkString;
    101     private String mDialogPanoramaFailedString;
    102     private String mDialogWaitingPreviousString;
    103 
    104     private int mPreviewUIWidth;
    105     private int mPreviewUIHeight;
    106     private boolean mUsingFrontCamera;
    107     private int mCameraPreviewWidth;
    108     private int mCameraPreviewHeight;
    109     private int mCameraState;
    110     private int mCaptureState;
    111     private PowerManager.WakeLock mPartialWakeLock;
    112     private MosaicFrameProcessor mMosaicFrameProcessor;
    113     private boolean mMosaicFrameProcessorInitialized;
    114     private AsyncTask <Void, Void, Void> mWaitProcessorTask;
    115     private long mTimeTaken;
    116     private Handler mMainHandler;
    117     private SurfaceTexture mCameraTexture;
    118     private boolean mThreadRunning;
    119     private boolean mCancelComputation;
    120     private float mHorizontalViewAngle;
    121     private float mVerticalViewAngle;
    122 
    123     // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of
    124     // getting a better image quality by the former.
    125     private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY;
    126 
    127     private PanoOrientationEventListener mOrientationEventListener;
    128     // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise
    129     // respectively.
    130     private int mDeviceOrientation;
    131     private int mDeviceOrientationAtCapture;
    132     private int mCameraOrientation;
    133     private int mOrientationCompensation;
    134 
    135     private SoundClips.Player mSoundPlayer;
    136 
    137     private Runnable mOnFrameAvailableRunnable;
    138 
    139     private CameraActivity mActivity;
    140     private View mRootView;
    141     private CameraProxy mCameraDevice;
    142     private boolean mPaused;
    143 
    144     private LocationManager mLocationManager;
    145     private OrientationManager mOrientationManager;
    146     private ComboPreferences mPreferences;
    147     private boolean mMosaicPreviewConfigured;
    148     private boolean mPreviewFocused = true;
    149 
    150     @Override
    151     public void onPreviewUIReady() {
    152         configMosaicPreview();
    153     }
    154 
    155     @Override
    156     public void onPreviewUIDestroyed() {
    157 
    158     }
    159 
    160     private class MosaicJpeg {
    161         public MosaicJpeg(byte[] data, int width, int height) {
    162             this.data = data;
    163             this.width = width;
    164             this.height = height;
    165             this.isValid = true;
    166         }
    167 
    168         public MosaicJpeg() {
    169             this.data = null;
    170             this.width = 0;
    171             this.height = 0;
    172             this.isValid = false;
    173         }
    174 
    175         public final byte[] data;
    176         public final int width;
    177         public final int height;
    178         public final boolean isValid;
    179     }
    180 
    181     private class PanoOrientationEventListener extends OrientationEventListener {
    182         public PanoOrientationEventListener(Context context) {
    183             super(context);
    184         }
    185 
    186         @Override
    187         public void onOrientationChanged(int orientation) {
    188             // We keep the last known orientation. So if the user first orient
    189             // the camera then point the camera to floor or sky, we still have
    190             // the correct orientation.
    191             if (orientation == ORIENTATION_UNKNOWN) return;
    192             mDeviceOrientation = CameraUtil.roundOrientation(orientation, mDeviceOrientation);
    193             // When the screen is unlocked, display rotation may change. Always
    194             // calculate the up-to-date orientationCompensation.
    195             int orientationCompensation = mDeviceOrientation
    196                     + CameraUtil.getDisplayRotation(mActivity) % 360;
    197             if (mOrientationCompensation != orientationCompensation) {
    198                 mOrientationCompensation = orientationCompensation;
    199             }
    200         }
    201     }
    202 
    203     @Override
    204     public void init(CameraActivity activity, View parent) {
    205         mActivity = activity;
    206         mRootView = parent;
    207 
    208         mOrientationManager = new OrientationManager(activity);
    209         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    210         mUI = new WideAnglePanoramaUI(mActivity, this, (ViewGroup) mRootView);
    211         mUI.setCaptureProgressOnDirectionChangeListener(
    212                 new PanoProgressBar.OnDirectionChangeListener() {
    213                     @Override
    214                     public void onDirectionChange(int direction) {
    215                         if (mCaptureState == CAPTURE_STATE_MOSAIC) {
    216                             mUI.showDirectionIndicators(direction);
    217                         }
    218                     }
    219                 });
    220 
    221         mContentResolver = mActivity.getContentResolver();
    222         // This runs in UI thread.
    223         mOnFrameAvailableRunnable = new Runnable() {
    224             @Override
    225             public void run() {
    226                 // Frames might still be available after the activity is paused.
    227                 // If we call onFrameAvailable after pausing, the GL thread will crash.
    228                 if (mPaused) return;
    229 
    230                 MosaicPreviewRenderer renderer = null;
    231                 synchronized (mRendererLock) {
    232                     if (mMosaicPreviewRenderer == null) {
    233                         return;
    234                     }
    235                     renderer = mMosaicPreviewRenderer;
    236                 }
    237                 if (mRootView.getVisibility() != View.VISIBLE) {
    238                     renderer.showPreviewFrameSync();
    239                     mRootView.setVisibility(View.VISIBLE);
    240                 } else {
    241                     if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {
    242                         renderer.showPreviewFrame();
    243                     } else {
    244                         renderer.alignFrameSync();
    245                         mMosaicFrameProcessor.processFrame();
    246                     }
    247                 }
    248             }
    249         };
    250 
    251         PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
    252         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama");
    253 
    254         mOrientationEventListener = new PanoOrientationEventListener(mActivity);
    255 
    256         mMosaicFrameProcessor = MosaicFrameProcessor.getInstance();
    257 
    258         Resources appRes = mActivity.getResources();
    259         mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview);
    260         mDialogTitle = appRes.getString(R.string.pano_dialog_title);
    261         mDialogOkString = appRes.getString(R.string.dialog_ok);
    262         mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed);
    263         mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous);
    264 
    265         mPreferences = new ComboPreferences(mActivity);
    266         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
    267         mLocationManager = new LocationManager(mActivity, null);
    268 
    269         mMainHandler = new Handler() {
    270             @Override
    271             public void handleMessage(Message msg) {
    272                 switch (msg.what) {
    273                     case MSG_LOW_RES_FINAL_MOSAIC_READY:
    274                         onBackgroundThreadFinished();
    275                         showFinalMosaic((Bitmap) msg.obj);
    276                         saveHighResMosaic();
    277                         break;
    278                     case MSG_GENERATE_FINAL_MOSAIC_ERROR:
    279                         onBackgroundThreadFinished();
    280                         if (mPaused) {
    281                             resetToPreviewIfPossible();
    282                         } else {
    283                             mUI.showAlertDialog(
    284                                     mDialogTitle, mDialogPanoramaFailedString,
    285                                     mDialogOkString, new Runnable() {
    286                                 @Override
    287                                 public void run() {
    288                                     resetToPreviewIfPossible();
    289                                 }
    290                             });
    291                         }
    292                         clearMosaicFrameProcessorIfNeeded();
    293                         break;
    294                     case MSG_END_DIALOG_RESET_TO_PREVIEW:
    295                         onBackgroundThreadFinished();
    296                         resetToPreviewIfPossible();
    297                         clearMosaicFrameProcessorIfNeeded();
    298                         break;
    299                     case MSG_CLEAR_SCREEN_DELAY:
    300                         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.
    301                                 FLAG_KEEP_SCREEN_ON);
    302                         break;
    303                     case MSG_RESET_TO_PREVIEW:
    304                         resetToPreviewIfPossible();
    305                         break;
    306                 }
    307             }
    308         };
    309     }
    310 
    311     @Override
    312     public void onPreviewFocusChanged(boolean previewFocused) {
    313         mPreviewFocused = previewFocused;
    314         mUI.onPreviewFocusChanged(previewFocused);
    315     }
    316 
    317     @Override
    318     public boolean arePreviewControlsVisible() {
    319         return mUI.arePreviewControlsVisible();
    320     }
    321 
    322     /**
    323      * Opens camera and sets the parameters.
    324      *
    325      * @return Whether the camera was opened successfully.
    326      */
    327     private boolean setupCamera() {
    328         if (!openCamera()) {
    329             return false;
    330         }
    331         Parameters parameters = mCameraDevice.getParameters();
    332         setupCaptureParams(parameters);
    333         configureCamera(parameters);
    334         return true;
    335     }
    336 
    337     private void releaseCamera() {
    338         if (mCameraDevice != null) {
    339             CameraHolder.instance().release();
    340             mCameraDevice = null;
    341             mCameraState = PREVIEW_STOPPED;
    342         }
    343     }
    344 
    345     /**
    346      * Opens the camera device. The back camera has priority over the front
    347      * one.
    348      *
    349      * @return Whether the camera was opened successfully.
    350      */
    351     private boolean openCamera() {
    352         int cameraId = CameraHolder.instance().getBackCameraId();
    353         // If there is no back camera, use the first camera. Camera id starts
    354         // from 0. Currently if a camera is not back facing, it is front facing.
    355         // This is also forward compatible if we have a new facing other than
    356         // back or front in the future.
    357         if (cameraId == -1) cameraId = 0;
    358         mCameraDevice = CameraUtil.openCamera(mActivity, cameraId,
    359                 mMainHandler, mActivity.getCameraOpenErrorCallback());
    360         if (mCameraDevice == null) {
    361             return false;
    362         }
    363         mCameraOrientation = CameraUtil.getCameraOrientation(cameraId);
    364         if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true;
    365         return 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                 mCameraPreviewWidth = w;
    386                 mCameraPreviewHeight = 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.d(TAG, "camera preview h = "
    404                     + mCameraPreviewHeight + " , w = " + mCameraPreviewWidth);
    405         parameters.setPreviewSize(mCameraPreviewWidth, mCameraPreviewHeight);
    406 
    407         List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
    408         int last = frameRates.size() - 1;
    409         int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
    410         int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
    411         parameters.setPreviewFpsRange(minFps, maxFps);
    412         Log.d(TAG, "preview fps: " + minFps + ", " + maxFps);
    413 
    414         List<String> supportedFocusModes = parameters.getSupportedFocusModes();
    415         if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) {
    416             parameters.setFocusMode(mTargetFocusMode);
    417         } else {
    418             // Use the default focus mode and log a message
    419             Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode +
    420                   " becuase the mode is not supported.");
    421         }
    422 
    423         parameters.set(CameraUtil.RECORDING_HINT, CameraUtil.FALSE);
    424 
    425         mHorizontalViewAngle = parameters.getHorizontalViewAngle();
    426         mVerticalViewAngle =  parameters.getVerticalViewAngle();
    427     }
    428 
    429     public int getPreviewBufSize() {
    430         PixelFormat pixelInfo = new PixelFormat();
    431         PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
    432         // TODO: remove this extra 32 byte after the driver bug is fixed.
    433         return (mCameraPreviewWidth * mCameraPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
    434     }
    435 
    436     private void configureCamera(Parameters parameters) {
    437         mCameraDevice.setParameters(parameters);
    438     }
    439 
    440     /**
    441      * Configures the preview renderer according to the dimension defined by
    442      * {@code mPreviewUIWidth} and {@code mPreviewUIHeight}.
    443      * Will stop the camera preview first.
    444      */
    445     private void configMosaicPreview() {
    446         if (mPreviewUIWidth == 0 || mPreviewUIHeight == 0
    447                 || mUI.getSurfaceTexture() == null) {
    448             return;
    449         }
    450 
    451         stopCameraPreview();
    452         synchronized (mRendererLock) {
    453             if (mMosaicPreviewRenderer != null) {
    454                 mMosaicPreviewRenderer.release();
    455             }
    456             mMosaicPreviewRenderer = null;
    457         }
    458         final boolean isLandscape =
    459                 (mActivity.getResources().getConfiguration().orientation ==
    460                         Configuration.ORIENTATION_LANDSCAPE);
    461         mUI.flipPreviewIfNeeded();
    462         MosaicPreviewRenderer renderer = new MosaicPreviewRenderer(
    463                 mUI.getSurfaceTexture(),
    464                 mPreviewUIWidth, mPreviewUIHeight, isLandscape);
    465         synchronized (mRendererLock) {
    466             mMosaicPreviewRenderer = renderer;
    467             mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture();
    468 
    469             if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) {
    470                 mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);
    471             }
    472             mRendererLock.notifyAll();
    473         }
    474         mMosaicPreviewConfigured = true;
    475         resetToPreviewIfPossible();
    476     }
    477 
    478     /**
    479      * Receives the layout change event from the preview area. So we can
    480      * initialize the mosaic preview renderer.
    481      */
    482     @Override
    483     public void onPreviewUILayoutChange(int l, int t, int r, int b) {
    484         Log.d(TAG, "layout change: " + (r - l) + "/" + (b - t));
    485         mPreviewUIWidth = r - l;
    486         mPreviewUIHeight = b - t;
    487         configMosaicPreview();
    488     }
    489 
    490     @Override
    491     public void onFrameAvailable(SurfaceTexture surface) {
    492         /* This function may be called by some random thread,
    493          * so let's be safe and jump back to ui thread.
    494          * No OpenGL calls can be done here. */
    495         mActivity.runOnUiThread(mOnFrameAvailableRunnable);
    496     }
    497 
    498     public void startCapture() {
    499         // Reset values so we can do this again.
    500         mCancelComputation = false;
    501         mTimeTaken = System.currentTimeMillis();
    502         mActivity.setSwipingEnabled(false);
    503         mCaptureState = CAPTURE_STATE_MOSAIC;
    504         mUI.onStartCapture();
    505 
    506         mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
    507             @Override
    508             public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
    509                     float progressX, float progressY) {
    510                 float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
    511                 float accumulatedVerticalAngle = progressY * mVerticalViewAngle;
    512                 if (isFinished
    513                         || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
    514                         || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {
    515                     stopCapture(false);
    516                 } else {
    517                     float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
    518                     float panningRateYInDegree = panningRateY * mVerticalViewAngle;
    519                     mUI.updateCaptureProgress(panningRateXInDegree, panningRateYInDegree,
    520                             accumulatedHorizontalAngle, accumulatedVerticalAngle,
    521                             PANNING_SPEED_THRESHOLD);
    522                 }
    523             }
    524         });
    525 
    526         mUI.resetCaptureProgress();
    527         // TODO: calculate the indicator width according to different devices to reflect the actual
    528         // angle of view of the camera device.
    529         mUI.setMaxCaptureProgress(DEFAULT_SWEEP_ANGLE);
    530         mUI.showCaptureProgress();
    531         mDeviceOrientationAtCapture = mDeviceOrientation;
    532         keepScreenOn();
    533         // TODO: mActivity.getOrientationManager().lockOrientation();
    534         mOrientationManager.lockOrientation();
    535         int degrees = CameraUtil.getDisplayRotation(mActivity);
    536         int cameraId = CameraHolder.instance().getBackCameraId();
    537         int orientation = CameraUtil.getDisplayOrientation(degrees, cameraId);
    538         mUI.setProgressOrientation(orientation);
    539     }
    540 
    541     private void stopCapture(boolean aborted) {
    542         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    543         mUI.onStopCapture();
    544 
    545         mMosaicFrameProcessor.setProgressListener(null);
    546         stopCameraPreview();
    547 
    548         mCameraTexture.setOnFrameAvailableListener(null);
    549 
    550         if (!aborted && !mThreadRunning) {
    551             mUI.showWaitingDialog(mPreparePreviewString);
    552             // Hide shutter button, shutter icon, etc when waiting for
    553             // panorama to stitch
    554             mUI.hideUI();
    555             runBackgroundThread(new Thread() {
    556                 @Override
    557                 public void run() {
    558                     MosaicJpeg jpeg = generateFinalMosaic(false);
    559 
    560                     if (jpeg != null && jpeg.isValid) {
    561                         Bitmap bitmap = null;
    562                         bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
    563                         mMainHandler.sendMessage(mMainHandler.obtainMessage(
    564                                 MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));
    565                     } else {
    566                         mMainHandler.sendMessage(mMainHandler.obtainMessage(
    567                                 MSG_END_DIALOG_RESET_TO_PREVIEW));
    568                     }
    569                 }
    570             });
    571         }
    572         keepScreenOnAwhile();
    573     }
    574 
    575     @Override
    576     public void onShutterButtonClick() {
    577         // If mCameraTexture == null then GL setup is not finished yet.
    578         // No buttons can be pressed.
    579         if (mPaused || mThreadRunning || mCameraTexture == null) {
    580             return;
    581         }
    582         // Since this button will stay on the screen when capturing, we need to check the state
    583         // right now.
    584         switch (mCaptureState) {
    585             case CAPTURE_STATE_VIEWFINDER:
    586                 final long storageSpaceBytes = mActivity.getStorageSpaceBytes();
    587                 if(storageSpaceBytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
    588                     Log.w(TAG, "Low storage warning: " + storageSpaceBytes);
    589                     return;
    590                 }
    591                 mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
    592                 startCapture();
    593                 break;
    594             case CAPTURE_STATE_MOSAIC:
    595                 mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
    596                 stopCapture(false);
    597                 break;
    598             default:
    599                 Log.w(TAG, "Unknown capture state: " + mCaptureState);
    600                 break;
    601         }
    602     }
    603 
    604     public void reportProgress() {
    605         mUI.resetSavingProgress();
    606         Thread t = new Thread() {
    607             @Override
    608             public void run() {
    609                 while (mThreadRunning) {
    610                     final int progress = mMosaicFrameProcessor.reportProgress(
    611                             true, mCancelComputation);
    612 
    613                     try {
    614                         synchronized (mWaitObject) {
    615                             mWaitObject.wait(50);
    616                         }
    617                     } catch (InterruptedException e) {
    618                         throw new RuntimeException("Panorama reportProgress failed", e);
    619                     }
    620                     // Update the progress bar
    621                     mActivity.runOnUiThread(new Runnable() {
    622                         @Override
    623                         public void run() {
    624                             mUI.updateSavingProgress(progress);
    625                         }
    626                     });
    627                 }
    628             }
    629         };
    630         t.start();
    631     }
    632 
    633     private int getCaptureOrientation() {
    634         // The panorama image returned from the library is oriented based on the
    635         // natural orientation of a camera. We need to set an orientation for the image
    636         // in its EXIF header, so the image can be displayed correctly.
    637         // The orientation is calculated from compensating the
    638         // device orientation at capture and the camera orientation respective to
    639         // the natural orientation of the device.
    640         int orientation;
    641         if (mUsingFrontCamera) {
    642             // mCameraOrientation is negative with respect to the front facing camera.
    643             // See document of android.hardware.Camera.Parameters.setRotation.
    644             orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360;
    645         } else {
    646             orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360;
    647         }
    648         return orientation;
    649     }
    650 
    651     /** The orientation of the camera image. The value is the angle that the camera
    652      *  image needs to be rotated clockwise so it shows correctly on the display
    653      *  in its natural orientation. It should be 0, 90, 180, or 270.*/
    654     public int getCameraOrientation() {
    655         return mCameraOrientation;
    656     }
    657 
    658     public void saveHighResMosaic() {
    659         runBackgroundThread(new Thread() {
    660             @Override
    661             public void run() {
    662                 mPartialWakeLock.acquire();
    663                 MosaicJpeg jpeg;
    664                 try {
    665                     jpeg = generateFinalMosaic(true);
    666                 } finally {
    667                     mPartialWakeLock.release();
    668                 }
    669 
    670                 if (jpeg == null) {  // Cancelled by user.
    671                     mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW);
    672                 } else if (!jpeg.isValid) {  // Error when generating mosaic.
    673                     mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
    674                 } else {
    675                     int orientation = getCaptureOrientation();
    676                     final Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation);
    677                     if (uri != null) {
    678                         mActivity.runOnUiThread(new Runnable() {
    679                             @Override
    680                             public void run() {
    681                                 mActivity.notifyNewMedia(uri);
    682                             }
    683                         });
    684                     }
    685                     mMainHandler.sendMessage(
    686                             mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW));
    687                 }
    688             }
    689         });
    690         reportProgress();
    691     }
    692 
    693     private void runBackgroundThread(Thread thread) {
    694         mThreadRunning = true;
    695         thread.start();
    696     }
    697 
    698     private void onBackgroundThreadFinished() {
    699         mThreadRunning = false;
    700         mUI.dismissAllDialogs();
    701     }
    702 
    703     private void cancelHighResComputation() {
    704         mCancelComputation = true;
    705         synchronized (mWaitObject) {
    706             mWaitObject.notify();
    707         }
    708     }
    709 
    710     // This function will be called upon the first camera frame is available.
    711     private void reset() {
    712         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    713 
    714         mOrientationManager.unlockOrientation();
    715         mUI.reset();
    716         mActivity.setSwipingEnabled(true);
    717         // Orientation change will trigger onLayoutChange->configMosaicPreview->
    718         // resetToPreview. Do not show the capture UI in film strip.
    719         if (mPreviewFocused) {
    720             mUI.showPreviewUI();
    721         }
    722         mMosaicFrameProcessor.reset();
    723     }
    724 
    725     private void resetToPreviewIfPossible() {
    726         reset();
    727         if (!mMosaicFrameProcessorInitialized
    728                 || mUI.getSurfaceTexture() == null
    729                 || !mMosaicPreviewConfigured) {
    730             return;
    731         }
    732         if (!mPaused) {
    733             startCameraPreview();
    734         }
    735     }
    736 
    737     private void showFinalMosaic(Bitmap bitmap) {
    738         mUI.showFinalMosaic(bitmap, getCaptureOrientation());
    739     }
    740 
    741     private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
    742         if (jpegData != null) {
    743             String filename = PanoUtil.createName(
    744                     mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
    745             String filepath = Storage.generateFilepath(filename);
    746 
    747             UsageStatistics.onEvent(UsageStatistics.COMPONENT_PANORAMA,
    748                     UsageStatistics.ACTION_CAPTURE_DONE, null, 0,
    749                     UsageStatistics.hashFileName(filename + ".jpg"));
    750 
    751             Location loc = mLocationManager.getCurrentLocation();
    752             ExifInterface exif = new ExifInterface();
    753             try {
    754                 exif.readExif(jpegData);
    755                 exif.addGpsDateTimeStampTag(mTimeTaken);
    756                 exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken,
    757                         TimeZone.getDefault());
    758                 exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
    759                         ExifInterface.getOrientationValueForRotation(orientation)));
    760                 writeLocation(loc, exif);
    761                 exif.writeExif(jpegData, filepath);
    762             } catch (IOException e) {
    763                 Log.e(TAG, "Cannot set exif for " + filepath, e);
    764                 Storage.writeFile(filepath, jpegData);
    765             }
    766             int jpegLength = (int) (new File(filepath).length());
    767             return Storage.addImage(mContentResolver, filename, mTimeTaken, loc, orientation,
    768                     jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG);
    769         }
    770         return null;
    771     }
    772 
    773     private static void writeLocation(Location location, ExifInterface exif) {
    774         if (location == null) {
    775             return;
    776         }
    777         exif.addGpsTags(location.getLatitude(), location.getLongitude());
    778         exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider()));
    779     }
    780 
    781     private void clearMosaicFrameProcessorIfNeeded() {
    782         if (!mPaused || mThreadRunning) return;
    783         // Only clear the processor if it is initialized by this activity
    784         // instance. Other activity instances may be using it.
    785         if (mMosaicFrameProcessorInitialized) {
    786             mMosaicFrameProcessor.clear();
    787             mMosaicFrameProcessorInitialized = false;
    788         }
    789     }
    790 
    791     private void initMosaicFrameProcessorIfNeeded() {
    792         if (mPaused || mThreadRunning) {
    793             return;
    794         }
    795 
    796         mMosaicFrameProcessor.initialize(
    797                 mCameraPreviewWidth, mCameraPreviewHeight, getPreviewBufSize());
    798         mMosaicFrameProcessorInitialized = true;
    799     }
    800 
    801     @Override
    802     public void onPauseBeforeSuper() {
    803         mPaused = true;
    804         if (mLocationManager != null) mLocationManager.recordLocation(false);
    805         mOrientationManager.pause();
    806     }
    807 
    808     @Override
    809     public void onPauseAfterSuper() {
    810         mOrientationEventListener.disable();
    811         if (mCameraDevice == null) {
    812             // Camera open failed. Nothing should be done here.
    813             return;
    814         }
    815         // Stop the capturing first.
    816         if (mCaptureState == CAPTURE_STATE_MOSAIC) {
    817             stopCapture(true);
    818             reset();
    819         }
    820         mUI.showPreviewCover();
    821         releaseCamera();
    822         synchronized (mRendererLock) {
    823             mCameraTexture = null;
    824 
    825             // The preview renderer might not have a chance to be initialized
    826             // before onPause().
    827             if (mMosaicPreviewRenderer != null) {
    828                 mMosaicPreviewRenderer.release();
    829                 mMosaicPreviewRenderer = null;
    830             }
    831         }
    832 
    833         clearMosaicFrameProcessorIfNeeded();
    834         if (mWaitProcessorTask != null) {
    835             mWaitProcessorTask.cancel(true);
    836             mWaitProcessorTask = null;
    837         }
    838         resetScreenOn();
    839         mUI.removeDisplayChangeListener();
    840         if (mSoundPlayer != null) {
    841             mSoundPlayer.release();
    842             mSoundPlayer = null;
    843         }
    844         System.gc();
    845     }
    846 
    847     @Override
    848     public void onConfigurationChanged(Configuration newConfig) {
    849         mUI.onConfigurationChanged(newConfig, mThreadRunning);
    850     }
    851 
    852     @Override
    853     public void onOrientationChanged(int orientation) {
    854     }
    855 
    856     @Override
    857     public void onResumeBeforeSuper() {
    858         mPaused = false;
    859     }
    860 
    861     @Override
    862     public void onResumeAfterSuper() {
    863         mOrientationEventListener.enable();
    864 
    865         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    866 
    867         if (!setupCamera()) {
    868             Log.e(TAG, "Failed to open camera, aborting");
    869             return;
    870         }
    871 
    872         // Set up sound playback for shutter button
    873         mSoundPlayer = SoundClips.getPlayer(mActivity);
    874 
    875         // Check if another panorama instance is using the mosaic frame processor.
    876         mUI.dismissAllDialogs();
    877         if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
    878             mUI.showWaitingDialog(mDialogWaitingPreviousString);
    879             // If stitching is still going on, make sure switcher and shutter button
    880             // are not showing
    881             mUI.hideUI();
    882             mWaitProcessorTask = new WaitProcessorTask().execute();
    883         } else {
    884             // Camera must be initialized before MosaicFrameProcessor is
    885             // initialized. The preview size has to be decided by camera device.
    886             initMosaicFrameProcessorIfNeeded();
    887             Point size = mUI.getPreviewAreaSize();
    888             mPreviewUIWidth = size.x;
    889             mPreviewUIHeight = size.y;
    890             configMosaicPreview();
    891             mActivity.updateStorageSpaceAndHint();
    892         }
    893         keepScreenOnAwhile();
    894 
    895         mOrientationManager.resume();
    896         // Initialize location service.
    897         boolean recordLocation = RecordLocationPreference.get(mPreferences,
    898                 mContentResolver);
    899         mLocationManager.recordLocation(recordLocation);
    900         mUI.initDisplayChangeListener();
    901         UsageStatistics.onContentViewChanged(
    902                 UsageStatistics.COMPONENT_CAMERA, "PanoramaModule");
    903     }
    904 
    905     /**
    906      * Generate the final mosaic image.
    907      *
    908      * @param highRes flag to indicate whether we want to get a high-res version.
    909      * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
    910      *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
    911      *         is an error in generating the final mosaic.
    912      */
    913     public MosaicJpeg generateFinalMosaic(boolean highRes) {
    914         int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
    915         if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
    916             return null;
    917         } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
    918             return new MosaicJpeg();
    919         }
    920 
    921         byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
    922         if (imageData == null) {
    923             Log.e(TAG, "getFinalMosaicNV21() returned null.");
    924             return new MosaicJpeg();
    925         }
    926 
    927         int len = imageData.length - 8;
    928         int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
    929                 + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
    930         int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
    931                 + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
    932         Log.d(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
    933 
    934         if (width <= 0 || height <= 0) {
    935             // TODO: pop up an error message indicating that the final result is not generated.
    936             Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
    937                     height);
    938             return new MosaicJpeg();
    939         }
    940 
    941         YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
    942         ByteArrayOutputStream out = new ByteArrayOutputStream();
    943         yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
    944         try {
    945             out.close();
    946         } catch (Exception e) {
    947             Log.e(TAG, "Exception in storing final mosaic", e);
    948             return new MosaicJpeg();
    949         }
    950         return new MosaicJpeg(out.toByteArray(), width, height);
    951     }
    952 
    953     private void startCameraPreview() {
    954         if (mCameraDevice == null) {
    955             // Camera open failed. Return.
    956             return;
    957         }
    958 
    959         if (mUI.getSurfaceTexture() == null) {
    960             // UI is not ready.
    961             return;
    962         }
    963 
    964         // This works around a driver issue. startPreview may fail if
    965         // stopPreview/setPreviewTexture/startPreview are called several times
    966         // in a row. mCameraTexture can be null after pressing home during
    967         // mosaic generation and coming back. Preview will be started later in
    968         // onLayoutChange->configMosaicPreview. This also reduces the latency.
    969         synchronized (mRendererLock) {
    970             if (mCameraTexture == null) return;
    971 
    972             // If we're previewing already, stop the preview first (this will
    973             // blank the screen).
    974             if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
    975 
    976             // Set the display orientation to 0, so that the underlying mosaic
    977             // library can always get undistorted mCameraPreviewWidth x mCameraPreviewHeight
    978             // image data from SurfaceTexture.
    979             mCameraDevice.setDisplayOrientation(0);
    980 
    981             mCameraTexture.setOnFrameAvailableListener(this);
    982             mCameraDevice.setPreviewTexture(mCameraTexture);
    983         }
    984         mCameraDevice.startPreview();
    985         mCameraState = PREVIEW_ACTIVE;
    986     }
    987 
    988     private void stopCameraPreview() {
    989         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
    990             mCameraDevice.stopPreview();
    991         }
    992         mCameraState = PREVIEW_STOPPED;
    993     }
    994 
    995     @Override
    996     public void onUserInteraction() {
    997         if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile();
    998     }
    999 
   1000     @Override
   1001     public boolean onBackPressed() {
   1002         // If panorama is generating low res or high res mosaic, ignore back
   1003         // key. So the activity will not be destroyed.
   1004         if (mThreadRunning) return true;
   1005         return false;
   1006     }
   1007 
   1008     private void resetScreenOn() {
   1009         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1010         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1011     }
   1012 
   1013     private void keepScreenOnAwhile() {
   1014         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1015         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1016         mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY);
   1017     }
   1018 
   1019     private void keepScreenOn() {
   1020         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1021         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1022     }
   1023 
   1024     private class WaitProcessorTask extends AsyncTask<Void, Void, Void> {
   1025         @Override
   1026         protected Void doInBackground(Void... params) {
   1027             synchronized (mMosaicFrameProcessor) {
   1028                 while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
   1029                     try {
   1030                         mMosaicFrameProcessor.wait();
   1031                     } catch (Exception e) {
   1032                         // ignore
   1033                     }
   1034                 }
   1035             }
   1036             mActivity.updateStorageSpace();
   1037             return null;
   1038         }
   1039 
   1040         @Override
   1041         protected void onPostExecute(Void result) {
   1042             mWaitProcessorTask = null;
   1043             mUI.dismissAllDialogs();
   1044             // TODO (shkong): mGLRootView.setVisibility(View.VISIBLE);
   1045             initMosaicFrameProcessorIfNeeded();
   1046             Point size = mUI.getPreviewAreaSize();
   1047             mPreviewUIWidth = size.x;
   1048             mPreviewUIHeight = size.y;
   1049             configMosaicPreview();
   1050             resetToPreviewIfPossible();
   1051             mActivity.updateStorageHint(mActivity.getStorageSpaceBytes());
   1052         }
   1053     }
   1054 
   1055     @Override
   1056     public void cancelHighResStitching() {
   1057         if (mPaused || mCameraTexture == null) return;
   1058         cancelHighResComputation();
   1059     }
   1060 
   1061     @Override
   1062     public void onStop() {
   1063     }
   1064 
   1065     @Override
   1066     public void installIntentFilter() {
   1067     }
   1068 
   1069     @Override
   1070     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1071     }
   1072 
   1073 
   1074     @Override
   1075     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1076         return false;
   1077     }
   1078 
   1079     @Override
   1080     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1081         return false;
   1082     }
   1083 
   1084     @Override
   1085     public void onSingleTapUp(View view, int x, int y) {
   1086     }
   1087 
   1088     @Override
   1089     public void onPreviewTextureCopied() {
   1090     }
   1091 
   1092     @Override
   1093     public void onCaptureTextureCopied() {
   1094     }
   1095 
   1096     @Override
   1097     public boolean updateStorageHintOnResume() {
   1098         return false;
   1099     }
   1100 
   1101     @Override
   1102     public void onShowSwitcherPopup() {
   1103     }
   1104 
   1105     @Override
   1106     public void onMediaSaveServiceConnected(MediaSaveService s) {
   1107         // do nothing.
   1108     }
   1109 }
   1110