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