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