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