PhotoModule.java revision f04692066fc7087560248b003c49e0377c056edf
1/*
2 * Copyright (C) 2012 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.app.Activity;
21import android.app.AlertDialog;
22import android.content.ContentProviderClient;
23import android.content.ContentResolver;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.SharedPreferences.Editor;
27import android.content.res.Configuration;
28import android.graphics.Bitmap;
29import android.graphics.SurfaceTexture;
30import android.hardware.Camera.CameraInfo;
31import android.hardware.Camera.Face;
32import android.hardware.Camera.FaceDetectionListener;
33import android.hardware.Camera.Parameters;
34import android.hardware.Camera.PictureCallback;
35import android.hardware.Camera.Size;
36import android.location.Location;
37import android.media.CameraProfile;
38import android.net.Uri;
39import android.os.Bundle;
40import android.os.ConditionVariable;
41import android.os.Handler;
42import android.os.Looper;
43import android.os.Message;
44import android.os.MessageQueue;
45import android.os.SystemClock;
46import android.provider.MediaStore;
47import android.util.Log;
48import android.view.Gravity;
49import android.view.KeyEvent;
50import android.view.LayoutInflater;
51import android.view.MotionEvent;
52import android.view.OrientationEventListener;
53import android.view.SurfaceHolder;
54import android.view.View;
55import android.view.View.OnClickListener;
56import android.view.ViewGroup;
57import android.view.WindowManager;
58import android.widget.FrameLayout;
59import android.widget.FrameLayout.LayoutParams;
60import android.widget.ImageView;
61import android.widget.TextView;
62import android.widget.Toast;
63
64import com.android.camera.CameraManager.CameraProxy;
65import com.android.camera.ui.AbstractSettingPopup;
66import com.android.camera.ui.FaceView;
67import com.android.camera.ui.PieRenderer;
68import com.android.camera.ui.PopupManager;
69import com.android.camera.ui.PreviewSurfaceView;
70import com.android.camera.ui.RenderOverlay;
71import com.android.camera.ui.Rotatable;
72import com.android.camera.ui.RotateLayout;
73import com.android.camera.ui.RotateTextToast;
74import com.android.camera.ui.TwoStateImageView;
75import com.android.camera.ui.ZoomRenderer;
76import com.android.gallery3d.app.CropImage;
77import com.android.gallery3d.common.ApiHelper;
78
79import java.io.File;
80import java.io.FileNotFoundException;
81import java.io.FileOutputStream;
82import java.io.IOException;
83import java.io.OutputStream;
84import java.util.ArrayList;
85import java.util.Collections;
86import java.util.Formatter;
87import java.util.List;
88
89public class PhotoModule
90    implements CameraModule,
91    FocusOverlayManager.Listener,
92    CameraPreference.OnPreferenceChangedListener,
93    LocationManager.Listener,
94    PreviewFrameLayout.OnSizeChangedListener,
95    ShutterButton.OnShutterButtonListener,
96    SurfaceHolder.Callback,
97    PieRenderer.PieListener,
98    CameraActivity.MenuListener {
99
100    private static final String TAG = "CAM_PhotoModule";
101
102    // We number the request code from 1000 to avoid collision with Gallery.
103    private static final int REQUEST_CROP = 1000;
104
105    private static final int SETUP_PREVIEW = 1;
106    private static final int FIRST_TIME_INIT = 2;
107    private static final int CLEAR_SCREEN_DELAY = 3;
108    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
109    private static final int CHECK_DISPLAY_ROTATION = 5;
110    private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
111    private static final int SWITCH_CAMERA = 7;
112    private static final int SWITCH_CAMERA_START_ANIMATION = 8;
113    private static final int CAMERA_OPEN_DONE = 9;
114    private static final int START_PREVIEW_DONE = 10;
115    private static final int OPEN_CAMERA_FAIL = 11;
116    private static final int CAMERA_DISABLED = 12;
117
118    // The subset of parameters we need to update in setCameraParameters().
119    private static final int UPDATE_PARAM_INITIALIZE = 1;
120    private static final int UPDATE_PARAM_ZOOM = 2;
121    private static final int UPDATE_PARAM_PREFERENCE = 4;
122    private static final int UPDATE_PARAM_ALL = -1;
123
124    // This is the timeout to keep the camera in onPause for the first time
125    // after screen on if the activity is started from secure lock screen.
126    private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
127
128    // copied from Camera hierarchy
129    private CameraActivity mActivity;
130    private View mRootView;
131    private CameraProxy mCameraDevice;
132    private int mCameraId;
133    private Parameters mParameters;
134    private boolean mPaused;
135    private AbstractSettingPopup mPopup;
136
137    // these are only used by Camera
138
139    // The activity is going to switch to the specified camera id. This is
140    // needed because texture copy is done in GL thread. -1 means camera is not
141    // switching.
142    protected int mPendingSwitchCameraId = -1;
143    private boolean mOpenCameraFail;
144    private boolean mCameraDisabled;
145
146    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
147    // needed to be updated in mUpdateSet.
148    private int mUpdateSet;
149
150    private static final int SCREEN_DELAY = 2 * 60 * 1000;
151
152    private int mZoomValue;  // The current zoom value.
153    private int mZoomMax;
154
155    private Parameters mInitialParams;
156    private boolean mFocusAreaSupported;
157    private boolean mMeteringAreaSupported;
158    private boolean mAeLockSupported;
159    private boolean mAwbLockSupported;
160    private boolean mContinousFocusSupported;
161
162    // The degrees of the device rotated clockwise from its natural orientation.
163    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
164    // The orientation compensation for icons and dialogs. Ex: if the value
165    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
166    private int mOrientationCompensation = 0;
167    // If mOrientationResetNeeded is set to be true, onOrientationChanged will reset
168    // the orientation of the on screen indicators to the current orientation compensation
169    // regardless of whether it's the same as the most recent orientation compensation
170    private boolean mOrientationResetNeeded;
171    private ComboPreferences mPreferences;
172
173    private static final String sTempCropFilename = "crop-temp";
174
175    private ContentProviderClient mMediaProviderClient;
176    private ShutterButton mShutterButton;
177    private boolean mFaceDetectionStarted = false;
178
179    private PreviewFrameLayout mPreviewFrameLayout;
180    private Object mSurfaceTexture;
181
182    // for API level 10
183    private PreviewSurfaceView mPreviewSurfaceView;
184    private volatile SurfaceHolder mCameraSurfaceHolder;
185
186    private RotateDialogController mRotateDialog;
187    private FaceView mFaceView;
188    private RenderOverlay mRenderOverlay;
189    private Rotatable mReviewCancelButton;
190    private Rotatable mReviewDoneButton;
191//    private View mReviewRetakeButton;
192
193    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
194    private String mCropValue;
195    private Uri mSaveUri;
196
197    // Small indicators which show the camera settings in the viewfinder.
198    private TextView mExposureIndicator;
199    private ImageView mGpsIndicator;
200    private ImageView mFlashIndicator;
201    private ImageView mSceneIndicator;
202    private ImageView mWhiteBalanceIndicator;
203    private ImageView mFocusIndicator;
204    // A view group that contains all the small indicators.
205    private Rotatable mOnScreenIndicators;
206
207    // We use a thread in ImageSaver to do the work of saving images. This
208    // reduces the shot-to-shot time.
209    private ImageSaver mImageSaver;
210    // Similarly, we use a thread to generate the name of the picture and insert
211    // it into MediaStore while picture taking is still in progress.
212    private ImageNamer mImageNamer;
213
214    private Runnable mDoSnapRunnable = new Runnable() {
215        @Override
216        public void run() {
217            onShutterButtonClick();
218        }
219    };
220
221    private final StringBuilder mBuilder = new StringBuilder();
222    private final Formatter mFormatter = new Formatter(mBuilder);
223    private final Object[] mFormatterArgs = new Object[1];
224
225    /**
226     * An unpublished intent flag requesting to return as soon as capturing
227     * is completed.
228     *
229     * TODO: consider publishing by moving into MediaStore.
230     */
231    private static final String EXTRA_QUICK_CAPTURE =
232            "android.intent.extra.quickCapture";
233
234    // The display rotation in degrees. This is only valid when mCameraState is
235    // not PREVIEW_STOPPED.
236    private int mDisplayRotation;
237    // The value for android.hardware.Camera.setDisplayOrientation.
238    private int mCameraDisplayOrientation;
239    // The value for UI components like indicators.
240    private int mDisplayOrientation;
241    // The value for android.hardware.Camera.Parameters.setRotation.
242    private int mJpegRotation;
243    private boolean mFirstTimeInitialized;
244    private boolean mIsImageCaptureIntent;
245
246    private static final int PREVIEW_STOPPED = 0;
247    private static final int IDLE = 1;  // preview is active
248    // Focus is in progress. The exact focus state is in Focus.java.
249    private static final int FOCUSING = 2;
250    private static final int SNAPSHOT_IN_PROGRESS = 3;
251    // Switching between cameras.
252    private static final int SWITCHING_CAMERA = 4;
253    private int mCameraState = PREVIEW_STOPPED;
254    private boolean mSnapshotOnIdle = false;
255
256    private ContentResolver mContentResolver;
257
258    private LocationManager mLocationManager;
259
260    private final ShutterCallback mShutterCallback = new ShutterCallback();
261    private final PostViewPictureCallback mPostViewPictureCallback =
262            new PostViewPictureCallback();
263    private final RawPictureCallback mRawPictureCallback =
264            new RawPictureCallback();
265    private final AutoFocusCallback mAutoFocusCallback =
266            new AutoFocusCallback();
267    private final Object mAutoFocusMoveCallback =
268            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
269            ? new AutoFocusMoveCallback()
270            : null;
271
272    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
273
274    private long mFocusStartTime;
275    private long mShutterCallbackTime;
276    private long mPostViewPictureCallbackTime;
277    private long mRawPictureCallbackTime;
278    private long mJpegPictureCallbackTime;
279    private long mOnResumeTime;
280    private byte[] mJpegImageData;
281
282    // These latency time are for the CameraLatency test.
283    public long mAutoFocusTime;
284    public long mShutterLag;
285    public long mShutterToPictureDisplayedTime;
286    public long mPictureDisplayedToJpegCallbackTime;
287    public long mJpegCallbackFinishTime;
288    public long mCaptureStartTime;
289
290    // This handles everything about focus.
291    private FocusOverlayManager mFocusManager;
292
293    private PieRenderer mPieRenderer;
294    private PhotoController mPhotoControl;
295
296    private ZoomRenderer mZoomRenderer;
297
298    private String mSceneMode;
299    private Toast mNotSelectableToast;
300
301    private final Handler mHandler = new MainHandler();
302    private PreferenceGroup mPreferenceGroup;
303
304    private boolean mQuickCapture;
305
306    CameraStartUpThread mCameraStartUpThread;
307    ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
308
309    private PreviewGestures mGestures;
310
311    // The purpose is not to block the main thread in onCreate and onResume.
312    private class CameraStartUpThread extends Thread {
313        private volatile boolean mCancelled;
314
315        public void cancel() {
316            mCancelled = true;
317        }
318
319        @Override
320        public void run() {
321            try {
322                // We need to check whether the activity is paused before long
323                // operations to ensure that onPause() can be done ASAP.
324                if (mCancelled) return;
325                mCameraDevice = Util.openCamera(mActivity, mCameraId);
326                mParameters = mCameraDevice.getParameters();
327                // Wait until all the initialization needed by startPreview are
328                // done.
329                mStartPreviewPrerequisiteReady.block();
330
331                initializeCapabilities();
332                if (mFocusManager == null) initializeFocusManager();
333                if (mCancelled) return;
334                setCameraParameters(UPDATE_PARAM_ALL);
335                mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
336                if (mCancelled) return;
337                startPreview();
338                mHandler.sendEmptyMessage(START_PREVIEW_DONE);
339                mOnResumeTime = SystemClock.uptimeMillis();
340                mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
341            } catch (CameraHardwareException e) {
342                mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
343            } catch (CameraDisabledException e) {
344                mHandler.sendEmptyMessage(CAMERA_DISABLED);
345            }
346        }
347    }
348
349    /**
350     * This Handler is used to post message back onto the main thread of the
351     * application
352     */
353    private class MainHandler extends Handler {
354        @Override
355        public void handleMessage(Message msg) {
356            switch (msg.what) {
357                case SETUP_PREVIEW: {
358                    setupPreview();
359                    break;
360                }
361
362                case CLEAR_SCREEN_DELAY: {
363                    mActivity.getWindow().clearFlags(
364                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
365                    break;
366                }
367
368                case FIRST_TIME_INIT: {
369                    initializeFirstTime();
370                    break;
371                }
372
373                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
374                    setCameraParametersWhenIdle(0);
375                    break;
376                }
377
378                case CHECK_DISPLAY_ROTATION: {
379                    // Set the display orientation if display rotation has changed.
380                    // Sometimes this happens when the device is held upside
381                    // down and camera app is opened. Rotation animation will
382                    // take some time and the rotation value we have got may be
383                    // wrong. Framework does not have a callback for this now.
384                    if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
385                        setDisplayOrientation();
386                    }
387                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
388                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
389                    }
390                    break;
391                }
392
393                case SHOW_TAP_TO_FOCUS_TOAST: {
394                    showTapToFocusToast();
395                    break;
396                }
397
398                case SWITCH_CAMERA: {
399                    switchCamera();
400                    break;
401                }
402
403                case SWITCH_CAMERA_START_ANIMATION: {
404                    ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
405                    break;
406                }
407
408                case CAMERA_OPEN_DONE: {
409                    initializeAfterCameraOpen();
410                    break;
411                }
412
413                case START_PREVIEW_DONE: {
414                    mCameraStartUpThread = null;
415                    setCameraState(IDLE);
416                    if (!ApiHelper.HAS_SURFACE_TEXTURE) {
417                        // This may happen if surfaceCreated has arrived.
418                        mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);
419                    }
420                    startFaceDetection();
421                    break;
422                }
423
424                case OPEN_CAMERA_FAIL: {
425                    mCameraStartUpThread = null;
426                    mOpenCameraFail = true;
427                    Util.showErrorAndFinish(mActivity,
428                            R.string.cannot_connect_camera);
429                    break;
430                }
431
432                case CAMERA_DISABLED: {
433                    mCameraStartUpThread = null;
434                    mCameraDisabled = true;
435                    Util.showErrorAndFinish(mActivity,
436                            R.string.camera_disabled);
437                    break;
438                }
439            }
440        }
441    }
442
443    @Override
444    public void init(CameraActivity activity, View parent, boolean reuseNail) {
445        mActivity = activity;
446        mRootView = parent;
447        mPreferences = new ComboPreferences(mActivity);
448        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
449        mCameraId = getPreferredCameraId(mPreferences);
450
451        mContentResolver = mActivity.getContentResolver();
452
453        // To reduce startup time, open the camera and start the preview in
454        // another thread.
455        mCameraStartUpThread = new CameraStartUpThread();
456        mCameraStartUpThread.start();
457
458        mActivity.getLayoutInflater().inflate(R.layout.photo_module, (ViewGroup) mRootView);
459        mActivity.setMenuListener(this);
460
461        // Surface texture is from camera screen nail and startPreview needs it.
462        // This must be done before startPreview.
463        mIsImageCaptureIntent = isImageCaptureIntent();
464        if (reuseNail) {
465            mActivity.reuseCameraScreenNail(!mIsImageCaptureIntent);
466        } else {
467            mActivity.createCameraScreenNail(!mIsImageCaptureIntent);
468        }
469
470        mPreferences.setLocalId(mActivity, mCameraId);
471        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
472        // we need to reset exposure for the preview
473        resetExposureCompensation();
474        // Starting the preview needs preferences, camera screen nail, and
475        // focus area indicator.
476        mStartPreviewPrerequisiteReady.open();
477
478        initializeControlByIntent();
479        mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
480        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
481        initializeMiscControls();
482        mLocationManager = new LocationManager(mActivity, this);
483        initOnScreenIndicator();
484
485        // Initialize to true to ensure that the on-screen indicators get their
486        // orientation set in onOrientationChanged.
487        mOrientationResetNeeded = true;
488        // Make sure all views are disabled before camera is open.
489//        enableCameraControls(false);
490        locationFirstRun();
491    }
492
493    // Prompt the user to pick to record location for the very first run of
494    // camera only
495    private void locationFirstRun() {
496        if (RecordLocationPreference.isSet(mPreferences)) {
497            return;
498        }
499        new AlertDialog.Builder(mActivity)
500            .setTitle(R.string.remember_location_title)
501            .setMessage(R.string.remember_location_prompt)
502            .setPositiveButton(R.string.remember_location_yes, new DialogInterface.OnClickListener() {
503                @Override
504                public void onClick(DialogInterface dialog, int arg1) {
505                    setLocationPreference(RecordLocationPreference.VALUE_ON);
506                }
507            })
508            .setNegativeButton(R.string.remember_location_no, new DialogInterface.OnClickListener() {
509                @Override
510                public void onClick(DialogInterface dialog, int arg1) {
511                    dialog.cancel();
512                }
513            })
514            .setOnCancelListener(new DialogInterface.OnCancelListener() {
515                @Override
516                public void onCancel(DialogInterface dialog) {
517                    setLocationPreference(RecordLocationPreference.VALUE_OFF);
518                }
519            })
520            .show();
521    }
522
523    private void setLocationPreference(String value) {
524        mPreferences.edit()
525            .putString(RecordLocationPreference.KEY, value)
526            .apply();
527        // TODO: Fix this to use the actual onSharedPreferencesChanged listener
528        // instead of invoking manually
529        onSharedPreferenceChanged();
530    }
531
532    private void initializeRenderOverlay() {
533        if (mPieRenderer != null) {
534            mRenderOverlay.addRenderer(mPieRenderer);
535            mFocusManager.setFocusRenderer(mPieRenderer);
536        }
537        if (mZoomRenderer != null) {
538            mRenderOverlay.addRenderer(mZoomRenderer);
539        }
540        if (mGestures != null) {
541            mGestures.clearTouchReceivers();
542            mGestures.setRenderOverlay(mRenderOverlay);
543            if (isImageCaptureIntent()) {
544                if (mReviewCancelButton != null) {
545                    mGestures.addTouchReceiver((View) mReviewCancelButton);
546                }
547                if (mReviewDoneButton != null) {
548                    mGestures.addTouchReceiver((View) mReviewDoneButton);
549                }
550            }
551        }
552        mRenderOverlay.requestLayout();
553    }
554
555    private void initializeAfterCameraOpen() {
556        if (mRenderOverlay != null) {
557            // render overlay is not initialized when setOrientationIndicator is called
558            // the first time, so do it here in UI thread
559            mRenderOverlay.setOrientation(mOrientationCompensation, false);
560        }
561        if (mPieRenderer == null) {
562            mPieRenderer = new PieRenderer(mActivity);
563            mPhotoControl = new PhotoController(mActivity, this, mPieRenderer);
564            mPhotoControl.setListener(this);
565            mPieRenderer.setPieListener(this);
566        }
567        if (mZoomRenderer == null) {
568            mZoomRenderer = new ZoomRenderer(mActivity);
569        }
570        if (mGestures == null) {
571            // this will handle gesture disambiguation and dispatching
572            mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
573        }
574        initializeRenderOverlay();
575        initializePhotoControl();
576
577        // These depend on camera parameters.
578        setPreviewFrameLayoutAspectRatio();
579        mFocusManager.setPreviewSize(mPreviewFrameLayout.getWidth(),
580                mPreviewFrameLayout.getHeight());
581        loadCameraPreferences();
582        initializeZoom();
583        updateOnScreenIndicators();
584        showTapToFocusToastIfNeeded();
585    }
586
587    private void initializePhotoControl() {
588        loadCameraPreferences();
589        mPhotoControl.initialize(mPreferenceGroup);
590        updateSceneModeUI();
591//        mIndicatorControlContainer.setListener(this);
592    }
593
594
595    private void resetExposureCompensation() {
596        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
597                CameraSettings.EXPOSURE_DEFAULT_VALUE);
598        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
599            Editor editor = mPreferences.edit();
600            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
601            editor.apply();
602        }
603    }
604
605    private void keepMediaProviderInstance() {
606        // We want to keep a reference to MediaProvider in camera's lifecycle.
607        // TODO: Utilize mMediaProviderClient instance to replace
608        // ContentResolver calls.
609        if (mMediaProviderClient == null) {
610            mMediaProviderClient = mContentResolver
611                    .acquireContentProviderClient(MediaStore.AUTHORITY);
612        }
613    }
614
615    // Snapshots can only be taken after this is called. It should be called
616    // once only. We could have done these things in onCreate() but we want to
617    // make preview screen appear as soon as possible.
618    private void initializeFirstTime() {
619        if (mFirstTimeInitialized) return;
620
621        // Initialize location service.
622        boolean recordLocation = RecordLocationPreference.get(
623                mPreferences, mContentResolver);
624        mLocationManager.recordLocation(recordLocation);
625
626        keepMediaProviderInstance();
627
628        // Initialize shutter button.
629        mShutterButton = mActivity.getShutterButton();
630        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
631        mShutterButton.setOnShutterButtonListener(this);
632        mShutterButton.setVisibility(View.VISIBLE);
633
634        mImageSaver = new ImageSaver();
635        mImageNamer = new ImageNamer();
636
637        mFirstTimeInitialized = true;
638        addIdleHandler();
639
640        mActivity.updateStorageSpaceAndHint();
641    }
642
643    private void showTapToFocusToastIfNeeded() {
644        // Show the tap to focus toast if this is the first start.
645        if (mFocusAreaSupported &&
646                mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
647            // Delay the toast for one second to wait for orientation.
648            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
649        }
650    }
651
652    private void addIdleHandler() {
653        MessageQueue queue = Looper.myQueue();
654        queue.addIdleHandler(new MessageQueue.IdleHandler() {
655            @Override
656            public boolean queueIdle() {
657                Storage.ensureOSXCompatible();
658                return false;
659            }
660        });
661    }
662
663    // If the activity is paused and resumed, this method will be called in
664    // onResume.
665    private void initializeSecondTime() {
666
667        // Start location update if needed.
668        boolean recordLocation = RecordLocationPreference.get(
669                mPreferences, mContentResolver);
670        mLocationManager.recordLocation(recordLocation);
671
672        mImageSaver = new ImageSaver();
673        mImageNamer = new ImageNamer();
674        initializeZoom();
675        keepMediaProviderInstance();
676        hidePostCaptureAlert();
677
678        if (mPhotoControl != null) {
679            mPhotoControl.reloadPreferences();
680        }
681    }
682
683    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
684        @Override
685        public void onZoomValueChanged(int index) {
686            // Not useful to change zoom value when the activity is paused.
687            if (mPaused) return;
688            mZoomValue = index;
689
690            // Set zoom parameters asynchronously
691            mParameters.setZoom(mZoomValue);
692            mCameraDevice.setParametersAsync(mParameters);
693        }
694
695        @Override
696        public void onZoomStart() {
697            if (mFocusManager != null) {
698                mFocusManager.resetTouchFocus();
699                mFocusManager.setEnabled(false);
700            }
701        }
702
703        @Override
704        public void onZoomEnd() {
705            if (mFocusManager != null) {
706                mFocusManager.setEnabled(true);
707            }
708        }
709    }
710
711    private void initializeZoom() {
712        if (!mParameters.isZoomSupported() || (mZoomRenderer == null)) return;
713        mZoomMax = mParameters.getMaxZoom();
714        // Currently we use immediate zoom for fast zooming to get better UX and
715        // there is no plan to take advantage of the smooth zoom.
716        if (mZoomRenderer != null) {
717            mZoomRenderer.setZoomMax(mZoomMax);
718            mZoomRenderer.setZoomIndex(mParameters.getZoom());
719            mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
720        }
721    }
722
723    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
724    @Override
725    public void startFaceDetection() {
726        if (!ApiHelper.HAS_FACE_DETECTION) return;
727        if (mFaceDetectionStarted) return;
728        if (mParameters.getMaxNumDetectedFaces() > 0) {
729            mFaceDetectionStarted = true;
730            mFaceView.clear();
731            mFaceView.setVisibility(View.VISIBLE);
732            mFaceView.setDisplayOrientation(mDisplayOrientation);
733            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
734            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
735            mFaceView.resume();
736            mFocusManager.setFaceView(mFaceView);
737            mCameraDevice.setFaceDetectionListener(new FaceDetectionListener() {
738                @Override
739                public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
740                    mFaceView.setFaces(faces);
741                }
742            });
743            mCameraDevice.startFaceDetection();
744        }
745    }
746
747    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
748    @Override
749    public void stopFaceDetection() {
750        if (!ApiHelper.HAS_FACE_DETECTION) return;
751        if (!mFaceDetectionStarted) return;
752        if (mParameters.getMaxNumDetectedFaces() > 0) {
753            mFaceDetectionStarted = false;
754            mCameraDevice.setFaceDetectionListener(null);
755            mCameraDevice.stopFaceDetection();
756            if (mFaceView != null) mFaceView.clear();
757        }
758    }
759
760    @Override
761    public boolean dispatchTouchEvent(MotionEvent m) {
762        if (mCameraState == SWITCHING_CAMERA) return true;
763        if (mPopup != null) {
764            return mActivity.superDispatchTouchEvent(m);
765        } else if (mGestures != null && mRenderOverlay != null) {
766            return mGestures.dispatchTouch(m);
767        }
768        return false;
769    }
770
771    private void initOnScreenIndicator() {
772        mGpsIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_gps_indicator);
773        mExposureIndicator = (TextView) mRootView.findViewById(R.id.onscreen_exposure_indicator);
774        mFlashIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_flash_indicator);
775        mSceneIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_scene_indicator);
776        mWhiteBalanceIndicator =
777                (ImageView) mRootView.findViewById(R.id.onscreen_white_balance_indicator);
778        mFocusIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_focus_indicator);
779    }
780
781    @Override
782    public void showGpsOnScreenIndicator(boolean hasSignal) {
783        if (mGpsIndicator == null) {
784            return;
785        }
786        if (hasSignal) {
787            mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_on);
788        } else {
789            mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_no_signal);
790        }
791        mGpsIndicator.setVisibility(View.VISIBLE);
792    }
793
794    @Override
795    public void hideGpsOnScreenIndicator() {
796        if (mGpsIndicator == null) {
797            return;
798        }
799        mGpsIndicator.setVisibility(View.GONE);
800    }
801
802    private void updateExposureOnScreenIndicator(int value) {
803        if (mExposureIndicator == null) {
804            return;
805        }
806        if (value == 0) {
807            mExposureIndicator.setText("");
808            mExposureIndicator.setVisibility(View.GONE);
809        } else {
810            float step = mParameters.getExposureCompensationStep();
811            mFormatterArgs[0] = value * step;
812            mBuilder.delete(0, mBuilder.length());
813            mFormatter.format("%+1.1f", mFormatterArgs);
814            String exposure = mFormatter.toString();
815            mExposureIndicator.setText(exposure);
816            mExposureIndicator.setVisibility(View.VISIBLE);
817        }
818    }
819
820    private void updateFlashOnScreenIndicator(String value) {
821        if (mFlashIndicator == null) {
822            return;
823        }
824        if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
825            mFlashIndicator.setVisibility(View.GONE);
826        } else {
827            mFlashIndicator.setVisibility(View.VISIBLE);
828            if (Parameters.FLASH_MODE_AUTO.equals(value)) {
829                mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_auto);
830            } else if (Parameters.FLASH_MODE_ON.equals(value)) {
831                mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_on);
832            } else {
833                // Should not happen.
834                mFlashIndicator.setVisibility(View.GONE);
835            }
836        }
837    }
838
839    private void updateSceneOnScreenIndicator(String value) {
840        if (mSceneIndicator == null) {
841            return;
842        }
843        boolean isGone = (value == null) || (Parameters.SCENE_MODE_AUTO.equals(value));
844        mSceneIndicator.setVisibility(isGone ? View.GONE : View.VISIBLE);
845    }
846
847    private void updateWhiteBalanceOnScreenIndicator(String value) {
848        if (mWhiteBalanceIndicator == null) {
849            return;
850        }
851        if (value == null || Parameters.WHITE_BALANCE_AUTO.equals(value)) {
852            mWhiteBalanceIndicator.setVisibility(View.GONE);
853        } else {
854            mWhiteBalanceIndicator.setVisibility(View.VISIBLE);
855            if (Parameters.WHITE_BALANCE_FLUORESCENT.equals(value)) {
856                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_fluorescent);
857            } else if (Parameters.WHITE_BALANCE_INCANDESCENT.equals(value)) {
858                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_incandescent);
859            } else if (Parameters.WHITE_BALANCE_DAYLIGHT.equals(value)) {
860                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_sunlight);
861            } else if (Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT.equals(value)) {
862                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_cloudy);
863            } else {
864                // Should not happen.
865                mWhiteBalanceIndicator.setVisibility(View.GONE);
866            }
867        }
868    }
869
870    private void updateFocusOnScreenIndicator(String value) {
871        if (mFocusIndicator == null || mPreferenceGroup == null) {
872            return;
873        }
874        // Do not show the indicator if users cannot choose.
875        if (mPreferenceGroup.findPreference(CameraSettings.KEY_FOCUS_MODE) == null) {
876            mFocusIndicator.setVisibility(View.GONE);
877        } else {
878            mFocusIndicator.setVisibility(View.VISIBLE);
879            if (Parameters.FOCUS_MODE_INFINITY.equals(value)) {
880                mFocusIndicator.setImageResource(R.drawable.ic_indicators_landscape);
881            } else if (Parameters.FOCUS_MODE_MACRO.equals(value)) {
882                mFocusIndicator.setImageResource(R.drawable.ic_indicators_macro);
883            } else {
884                // Should not happen.
885                mFocusIndicator.setVisibility(View.GONE);
886            }
887        }
888    }
889
890    private void updateOnScreenIndicators() {
891        updateSceneOnScreenIndicator(mParameters.getSceneMode());
892        updateExposureOnScreenIndicator(CameraSettings.readExposure(mPreferences));
893        updateFlashOnScreenIndicator(mParameters.getFlashMode());
894        updateWhiteBalanceOnScreenIndicator(mParameters.getWhiteBalance());
895        updateFocusOnScreenIndicator(mParameters.getFocusMode());
896    }
897
898    private final class ShutterCallback
899            implements android.hardware.Camera.ShutterCallback {
900        @Override
901        public void onShutter() {
902            mShutterCallbackTime = System.currentTimeMillis();
903            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
904            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
905        }
906    }
907
908    private final class PostViewPictureCallback implements PictureCallback {
909        @Override
910        public void onPictureTaken(
911                byte [] data, android.hardware.Camera camera) {
912            mPostViewPictureCallbackTime = System.currentTimeMillis();
913            Log.v(TAG, "mShutterToPostViewCallbackTime = "
914                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
915                    + "ms");
916        }
917    }
918
919    private final class RawPictureCallback implements PictureCallback {
920        @Override
921        public void onPictureTaken(
922                byte [] rawData, android.hardware.Camera camera) {
923            mRawPictureCallbackTime = System.currentTimeMillis();
924            Log.v(TAG, "mShutterToRawCallbackTime = "
925                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
926        }
927    }
928
929    private final class JpegPictureCallback implements PictureCallback {
930        Location mLocation;
931
932        public JpegPictureCallback(Location loc) {
933            mLocation = loc;
934        }
935
936        @Override
937        public void onPictureTaken(
938                final byte [] jpegData, final android.hardware.Camera camera) {
939            if (mPaused) {
940                return;
941            }
942
943            mJpegPictureCallbackTime = System.currentTimeMillis();
944            // If postview callback has arrived, the captured image is displayed
945            // in postview callback. If not, the captured image is displayed in
946            // raw picture callback.
947            if (mPostViewPictureCallbackTime != 0) {
948                mShutterToPictureDisplayedTime =
949                        mPostViewPictureCallbackTime - mShutterCallbackTime;
950                mPictureDisplayedToJpegCallbackTime =
951                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
952            } else {
953                mShutterToPictureDisplayedTime =
954                        mRawPictureCallbackTime - mShutterCallbackTime;
955                mPictureDisplayedToJpegCallbackTime =
956                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
957            }
958            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
959                    + mPictureDisplayedToJpegCallbackTime + "ms");
960
961            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
962            if (!mIsImageCaptureIntent) {
963                if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
964                    setupPreview();
965                } else {
966                    // Camera HAL of some devices have a bug. Starting preview
967                    // immediately after taking a picture will fail. Wait some
968                    // time before starting the preview.
969                    mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
970                }
971            }
972
973            if (!mIsImageCaptureIntent) {
974                // Calculate the width and the height of the jpeg.
975                Size s = mParameters.getPictureSize();
976                int orientation = Exif.getOrientation(jpegData);
977                int width, height;
978                if ((mJpegRotation + orientation) % 180 == 0) {
979                    width = s.width;
980                    height = s.height;
981                } else {
982                    width = s.height;
983                    height = s.width;
984                }
985                Uri uri = mImageNamer.getUri();
986                mActivity.addSecureAlbumItemIfNeeded(false, uri);
987                String title = mImageNamer.getTitle();
988                mImageSaver.addImage(jpegData, uri, title, mLocation,
989                        width, height, orientation);
990            } else {
991                mJpegImageData = jpegData;
992                if (!mQuickCapture) {
993                    showPostCaptureAlert();
994                } else {
995                    doAttach();
996                }
997            }
998
999            // Check this in advance of each shot so we don't add to shutter
1000            // latency. It's true that someone else could write to the SD card in
1001            // the mean time and fill it, but that could have happened between the
1002            // shutter press and saving the JPEG too.
1003            mActivity.updateStorageSpaceAndHint();
1004
1005            long now = System.currentTimeMillis();
1006            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
1007            Log.v(TAG, "mJpegCallbackFinishTime = "
1008                    + mJpegCallbackFinishTime + "ms");
1009            mJpegPictureCallbackTime = 0;
1010        }
1011    }
1012
1013    private final class AutoFocusCallback
1014            implements android.hardware.Camera.AutoFocusCallback {
1015        @Override
1016        public void onAutoFocus(
1017                boolean focused, android.hardware.Camera camera) {
1018            if (mPaused) return;
1019
1020            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
1021            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
1022            setCameraState(IDLE);
1023            mFocusManager.onAutoFocus(focused);
1024        }
1025    }
1026
1027    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
1028    private final class AutoFocusMoveCallback
1029            implements android.hardware.Camera.AutoFocusMoveCallback {
1030        @Override
1031        public void onAutoFocusMoving(
1032            boolean moving, android.hardware.Camera camera) {
1033                mFocusManager.onAutoFocusMoving(moving);
1034        }
1035    }
1036
1037    // Each SaveRequest remembers the data needed to save an image.
1038    private static class SaveRequest {
1039        byte[] data;
1040        Uri uri;
1041        String title;
1042        Location loc;
1043        int width, height;
1044        int orientation;
1045    }
1046
1047    // We use a queue to store the SaveRequests that have not been completed
1048    // yet. The main thread puts the request into the queue. The saver thread
1049    // gets it from the queue, does the work, and removes it from the queue.
1050    //
1051    // The main thread needs to wait for the saver thread to finish all the work
1052    // in the queue, when the activity's onPause() is called, we need to finish
1053    // all the work, so other programs (like Gallery) can see all the images.
1054    //
1055    // If the queue becomes too long, adding a new request will block the main
1056    // thread until the queue length drops below the threshold (QUEUE_LIMIT).
1057    // If we don't do this, we may face several problems: (1) We may OOM
1058    // because we are holding all the jpeg data in memory. (2) We may ANR
1059    // when we need to wait for saver thread finishing all the work (in
1060    // onPause() or gotoGallery()) because the time to finishing a long queue
1061    // of work may be too long.
1062    private class ImageSaver extends Thread {
1063        private static final int QUEUE_LIMIT = 3;
1064
1065        private ArrayList<SaveRequest> mQueue;
1066        private boolean mStop;
1067
1068        // Runs in main thread
1069        public ImageSaver() {
1070            mQueue = new ArrayList<SaveRequest>();
1071            start();
1072        }
1073
1074        // Runs in main thread
1075        public void addImage(final byte[] data, Uri uri, String title,
1076                Location loc, int width, int height, int orientation) {
1077            SaveRequest r = new SaveRequest();
1078            r.data = data;
1079            r.uri = uri;
1080            r.title = title;
1081            r.loc = (loc == null) ? null : new Location(loc);  // make a copy
1082            r.width = width;
1083            r.height = height;
1084            r.orientation = orientation;
1085            synchronized (this) {
1086                while (mQueue.size() >= QUEUE_LIMIT) {
1087                    try {
1088                        wait();
1089                    } catch (InterruptedException ex) {
1090                        // ignore.
1091                    }
1092                }
1093                mQueue.add(r);
1094                notifyAll();  // Tell saver thread there is new work to do.
1095            }
1096        }
1097
1098        // Runs in saver thread
1099        @Override
1100        public void run() {
1101            while (true) {
1102                SaveRequest r;
1103                synchronized (this) {
1104                    if (mQueue.isEmpty()) {
1105                        notifyAll();  // notify main thread in waitDone
1106
1107                        // Note that we can only stop after we saved all images
1108                        // in the queue.
1109                        if (mStop) break;
1110
1111                        try {
1112                            wait();
1113                        } catch (InterruptedException ex) {
1114                            // ignore.
1115                        }
1116                        continue;
1117                    }
1118                    r = mQueue.get(0);
1119                }
1120                storeImage(r.data, r.uri, r.title, r.loc, r.width, r.height,
1121                        r.orientation);
1122                synchronized (this) {
1123                    mQueue.remove(0);
1124                    notifyAll();  // the main thread may wait in addImage
1125                }
1126            }
1127        }
1128
1129        // Runs in main thread
1130        public void waitDone() {
1131            synchronized (this) {
1132                while (!mQueue.isEmpty()) {
1133                    try {
1134                        wait();
1135                    } catch (InterruptedException ex) {
1136                        // ignore.
1137                    }
1138                }
1139            }
1140        }
1141
1142        // Runs in main thread
1143        public void finish() {
1144            waitDone();
1145            synchronized (this) {
1146                mStop = true;
1147                notifyAll();
1148            }
1149            try {
1150                join();
1151            } catch (InterruptedException ex) {
1152                // ignore.
1153            }
1154        }
1155
1156        // Runs in saver thread
1157        private void storeImage(final byte[] data, Uri uri, String title,
1158                Location loc, int width, int height, int orientation) {
1159            boolean ok = Storage.updateImage(mContentResolver, uri, title, loc,
1160                    orientation, data, width, height);
1161            if (ok) {
1162                Util.broadcastNewPicture(mActivity, uri);
1163            }
1164        }
1165    }
1166
1167    private static class ImageNamer extends Thread {
1168        private boolean mRequestPending;
1169        private ContentResolver mResolver;
1170        private long mDateTaken;
1171        private int mWidth, mHeight;
1172        private boolean mStop;
1173        private Uri mUri;
1174        private String mTitle;
1175
1176        // Runs in main thread
1177        public ImageNamer() {
1178            start();
1179        }
1180
1181        // Runs in main thread
1182        public synchronized void prepareUri(ContentResolver resolver,
1183                long dateTaken, int width, int height, int rotation) {
1184            if (rotation % 180 != 0) {
1185                int tmp = width;
1186                width = height;
1187                height = tmp;
1188            }
1189            mRequestPending = true;
1190            mResolver = resolver;
1191            mDateTaken = dateTaken;
1192            mWidth = width;
1193            mHeight = height;
1194            notifyAll();
1195        }
1196
1197        // Runs in main thread
1198        public synchronized Uri getUri() {
1199            // wait until the request is done.
1200            while (mRequestPending) {
1201                try {
1202                    wait();
1203                } catch (InterruptedException ex) {
1204                    // ignore.
1205                }
1206            }
1207
1208            // return the uri generated
1209            Uri uri = mUri;
1210            mUri = null;
1211            return uri;
1212        }
1213
1214        // Runs in main thread, should be called after getUri().
1215        public synchronized String getTitle() {
1216            return mTitle;
1217        }
1218
1219        // Runs in namer thread
1220        @Override
1221        public synchronized void run() {
1222            while (true) {
1223                if (mStop) break;
1224                if (!mRequestPending) {
1225                    try {
1226                        wait();
1227                    } catch (InterruptedException ex) {
1228                        // ignore.
1229                    }
1230                    continue;
1231                }
1232                cleanOldUri();
1233                generateUri();
1234                mRequestPending = false;
1235                notifyAll();
1236            }
1237            cleanOldUri();
1238        }
1239
1240        // Runs in main thread
1241        public synchronized void finish() {
1242            mStop = true;
1243            notifyAll();
1244        }
1245
1246        // Runs in namer thread
1247        private void generateUri() {
1248            mTitle = Util.createJpegName(mDateTaken);
1249            mUri = Storage.newImage(mResolver, mTitle, mDateTaken, mWidth, mHeight);
1250        }
1251
1252        // Runs in namer thread
1253        private void cleanOldUri() {
1254            if (mUri == null) return;
1255            Storage.deleteImage(mResolver, mUri);
1256            mUri = null;
1257        }
1258    }
1259
1260    private void setCameraState(int state) {
1261        mCameraState = state;
1262        switch (state) {
1263            case PREVIEW_STOPPED:
1264            case SNAPSHOT_IN_PROGRESS:
1265            case FOCUSING:
1266            case SWITCHING_CAMERA:
1267//                enableCameraControls(false);
1268                break;
1269            case IDLE:
1270//                enableCameraControls(true);
1271                break;
1272        }
1273    }
1274
1275    @Override
1276    public boolean capture() {
1277        // If we are already in the middle of taking a snapshot then ignore.
1278        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1279                || mCameraState == SWITCHING_CAMERA) {
1280            return false;
1281        }
1282        mCaptureStartTime = System.currentTimeMillis();
1283        mPostViewPictureCallbackTime = 0;
1284        mJpegImageData = null;
1285
1286        // Set rotation and gps data.
1287        mJpegRotation = Util.getJpegRotation(mCameraId, mOrientation);
1288        mParameters.setRotation(mJpegRotation);
1289        Location loc = mLocationManager.getCurrentLocation();
1290        Util.setGpsParameters(mParameters, loc);
1291        mCameraDevice.setParameters(mParameters);
1292
1293        try {
1294            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
1295                    mPostViewPictureCallback, new JpegPictureCallback(loc));
1296        } catch (RuntimeException e) {
1297            Log.w(TAG, "take picture failed; mCameraState=" + mCameraState
1298                    + ", mFocusManager.mState=" + mFocusManager.getFocusState());
1299            throw e;
1300        }
1301
1302        Size size = mParameters.getPictureSize();
1303        mImageNamer.prepareUri(mContentResolver, mCaptureStartTime,
1304                size.width, size.height, mJpegRotation);
1305
1306        if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent) {
1307            // Start capture animation.
1308            ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(getCameraRotation());
1309        }
1310        mFaceDetectionStarted = false;
1311        setCameraState(SNAPSHOT_IN_PROGRESS);
1312        return true;
1313    }
1314
1315    private int getCameraRotation() {
1316        return (mOrientationCompensation - mDisplayRotation + 360) % 360;
1317    }
1318
1319    @Override
1320    public void setFocusParameters() {
1321        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1322    }
1323
1324    private int getPreferredCameraId(ComboPreferences preferences) {
1325        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
1326        if (intentCameraId != -1) {
1327            // Testing purpose. Launch a specific camera through the intent
1328            // extras.
1329            return intentCameraId;
1330        } else {
1331            return CameraSettings.readPreferredCameraId(preferences);
1332        }
1333    }
1334
1335
1336    @Override
1337    public void onFullScreenChanged(boolean full) {
1338        if (mPopup != null) {
1339            dismissPopup(false);
1340        }
1341        if (mGestures != null) {
1342            mGestures.setEnabled(full);
1343        }
1344        if (mFocusManager != null) {
1345            mFocusManager.resetTouchFocus();
1346            mFocusManager.setEnabled(full);
1347        }
1348        if (ApiHelper.HAS_SURFACE_TEXTURE) {
1349            if (mActivity.mCameraScreenNail != null) {
1350                ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
1351            }
1352            return;
1353        }
1354        if (full) {
1355            mPreviewSurfaceView.expand();
1356        } else {
1357            mPreviewSurfaceView.shrink();
1358        }
1359    }
1360
1361    @Override
1362    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
1363        Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
1364                + height);
1365    }
1366
1367    @Override
1368    public void surfaceCreated(SurfaceHolder holder) {
1369        Log.v(TAG, "surfaceCreated: " + holder);
1370        mCameraSurfaceHolder = holder;
1371        // Do not access the camera if camera start up thread is not finished.
1372        if (mCameraDevice == null || mCameraStartUpThread != null) return;
1373
1374        mCameraDevice.setPreviewDisplayAsync(holder);
1375        // This happens when onConfigurationChanged arrives, surface has been
1376        // destroyed, and there is no onFullScreenChanged.
1377        if (mCameraState == PREVIEW_STOPPED) {
1378            setupPreview();
1379        }
1380    }
1381
1382    @Override
1383    public void surfaceDestroyed(SurfaceHolder holder) {
1384        Log.v(TAG, "surfaceDestroyed: " + holder);
1385        mCameraSurfaceHolder = null;
1386        stopPreview();
1387    }
1388
1389    private void updateSceneModeUI() {
1390        // If scene mode is set, we cannot set flash mode, white balance, and
1391        // focus mode, instead, we read it from driver
1392        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1393            overrideCameraSettings(mParameters.getFlashMode(),
1394                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
1395        } else {
1396            overrideCameraSettings(null, null, null);
1397        }
1398    }
1399
1400    private void overrideCameraSettings(final String flashMode,
1401            final String whiteBalance, final String focusMode) {
1402        if (mPhotoControl != null) {
1403//            mPieControl.enableFilter(true);
1404            mPhotoControl.overrideSettings(
1405                    CameraSettings.KEY_FLASH_MODE, flashMode,
1406                    CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1407                    CameraSettings.KEY_FOCUS_MODE, focusMode);
1408//            mPieControl.enableFilter(false);
1409        }
1410    }
1411
1412    private void loadCameraPreferences() {
1413        CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
1414                mCameraId, CameraHolder.instance().getCameraInfo());
1415        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1416    }
1417
1418    @Override
1419    public boolean collapseCameraControls() {
1420        // Remove all the popups/dialog boxes
1421        boolean ret = false;
1422        if(mRotateDialog != null && mRotateDialog.getVisibility() == View.VISIBLE) {
1423            mRotateDialog.dismissDialog();
1424            ret = true;
1425        }
1426        if (mPopup != null) {
1427            dismissPopup(false);
1428            ret = true;
1429        }
1430        return ret;
1431    }
1432
1433    public boolean removeTopLevelPopup() {
1434        // Remove the top level popup or dialog box and return true if there's any
1435        if (mRotateDialog != null && mRotateDialog.getVisibility() == View.VISIBLE) {
1436            mRotateDialog.dismissDialog();
1437            return true;
1438        }
1439        if (mPopup != null) {
1440            dismissPopup(true);
1441            return true;
1442        }
1443        return false;
1444    }
1445
1446    @Override
1447    public void onOrientationChanged(int orientation) {
1448        // We keep the last known orientation. So if the user first orient
1449        // the camera then point the camera to floor or sky, we still have
1450        // the correct orientation.
1451        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
1452        mOrientation = Util.roundOrientation(orientation, mOrientation);
1453        // When the screen is unlocked, display rotation may change. Always
1454        // calculate the up-to-date orientationCompensation.
1455        int orientationCompensation =
1456                (mOrientation + Util.getDisplayRotation(mActivity)) % 360;
1457        if (mOrientationCompensation != orientationCompensation || mOrientationResetNeeded) {
1458            mOrientationCompensation = orientationCompensation;
1459            setOrientationIndicator(mOrientationCompensation, true);
1460            mOrientationResetNeeded = false;
1461        }
1462
1463        // Show the toast after getting the first orientation changed.
1464        if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
1465            mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
1466            showTapToFocusToast();
1467        }
1468    }
1469
1470    private void setOrientationIndicator(int orientation, boolean animation) {
1471        Rotatable[] indicators = {
1472                mRenderOverlay, mFaceView,
1473                mReviewDoneButton, mRotateDialog, mOnScreenIndicators};
1474        for (Rotatable indicator : indicators) {
1475            if (indicator != null) indicator.setOrientation(orientation, animation);
1476        }
1477
1478        // We change the orientation of the review cancel button only for tablet
1479        // UI because there's a label along with the X icon. For phone UI, we
1480        // don't change the orientation because there's only a symmetrical X
1481        // icon.
1482        if (mReviewCancelButton instanceof RotateLayout) {
1483            mReviewCancelButton.setOrientation(orientation, animation);
1484        }
1485        if (mPopup != null) {
1486            mPopup.setOrientation(orientation, animation);
1487        }
1488        if (mGestures != null) {
1489            mGestures.setOrientation(orientation);
1490        }
1491    }
1492
1493    @Override
1494    public void onStop() {
1495        if (mMediaProviderClient != null) {
1496            mMediaProviderClient.release();
1497            mMediaProviderClient = null;
1498        }
1499    }
1500
1501    // onClick handler for R.id.btn_retake
1502    @OnClickAttr
1503    public void onReviewRetakeClicked(View v) {
1504        if (mPaused) return;
1505
1506        hidePostCaptureAlert();
1507        setupPreview();
1508    }
1509
1510    // onClick handler for R.id.btn_done
1511    @OnClickAttr
1512    public void onReviewDoneClicked(View v) {
1513        doAttach();
1514    }
1515
1516    // onClick handler for R.id.btn_cancel
1517    @OnClickAttr
1518    public void onReviewCancelClicked(View v) {
1519        doCancel();
1520    }
1521
1522    private void doAttach() {
1523        if (mPaused) {
1524            return;
1525        }
1526
1527        byte[] data = mJpegImageData;
1528
1529        if (mCropValue == null) {
1530            // First handle the no crop case -- just return the value.  If the
1531            // caller specifies a "save uri" then write the data to its
1532            // stream. Otherwise, pass back a scaled down version of the bitmap
1533            // directly in the extras.
1534            if (mSaveUri != null) {
1535                OutputStream outputStream = null;
1536                try {
1537                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1538                    outputStream.write(data);
1539                    outputStream.close();
1540
1541                    mActivity.setResultEx(Activity.RESULT_OK);
1542                    mActivity.finish();
1543                } catch (IOException ex) {
1544                    // ignore exception
1545                } finally {
1546                    Util.closeSilently(outputStream);
1547                }
1548            } else {
1549                int orientation = Exif.getOrientation(data);
1550                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1551                bitmap = Util.rotate(bitmap, orientation);
1552                mActivity.setResultEx(Activity.RESULT_OK,
1553                        new Intent("inline-data").putExtra("data", bitmap));
1554                mActivity.finish();
1555            }
1556        } else {
1557            // Save the image to a temp file and invoke the cropper
1558            Uri tempUri = null;
1559            FileOutputStream tempStream = null;
1560            try {
1561                File path = mActivity.getFileStreamPath(sTempCropFilename);
1562                path.delete();
1563                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1564                tempStream.write(data);
1565                tempStream.close();
1566                tempUri = Uri.fromFile(path);
1567            } catch (FileNotFoundException ex) {
1568                mActivity.setResultEx(Activity.RESULT_CANCELED);
1569                mActivity.finish();
1570                return;
1571            } catch (IOException ex) {
1572                mActivity.setResultEx(Activity.RESULT_CANCELED);
1573                mActivity.finish();
1574                return;
1575            } finally {
1576                Util.closeSilently(tempStream);
1577            }
1578
1579            Bundle newExtras = new Bundle();
1580            if (mCropValue.equals("circle")) {
1581                newExtras.putString("circleCrop", "true");
1582            }
1583            if (mSaveUri != null) {
1584                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1585            } else {
1586                newExtras.putBoolean("return-data", true);
1587            }
1588            if (mActivity.isSecureCamera()) {
1589                newExtras.putBoolean(CropImage.KEY_SHOW_WHEN_LOCKED, true);
1590            }
1591
1592            Intent cropIntent = new Intent("com.android.camera.action.CROP");
1593
1594            cropIntent.setData(tempUri);
1595            cropIntent.putExtras(newExtras);
1596
1597            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1598        }
1599    }
1600
1601    private void doCancel() {
1602        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1603        mActivity.finish();
1604    }
1605
1606    @Override
1607    public void onShutterButtonFocus(boolean pressed) {
1608        if (mPaused || collapseCameraControls()
1609                || (mCameraState == SNAPSHOT_IN_PROGRESS)
1610                || (mCameraState == PREVIEW_STOPPED)) return;
1611
1612        // Do not do focus if there is not enough storage.
1613        if (pressed && !canTakePicture()) return;
1614
1615        if (pressed) {
1616            mFocusManager.onShutterDown();
1617        } else {
1618            mFocusManager.onShutterUp();
1619        }
1620    }
1621
1622    @Override
1623    public void onShutterButtonClick() {
1624        if (mPaused || collapseCameraControls()
1625                || (mCameraState == SWITCHING_CAMERA)
1626                || (mCameraState == PREVIEW_STOPPED)) return;
1627
1628        // Do not take the picture if there is not enough storage.
1629        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
1630            Log.i(TAG, "Not enough space or storage not ready. remaining="
1631                    + mActivity.getStorageSpace());
1632            return;
1633        }
1634        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1635
1636        // If the user wants to do a snapshot while the previous one is still
1637        // in progress, remember the fact and do it after we finish the previous
1638        // one and re-start the preview. Snapshot in progress also includes the
1639        // state that autofocus is focusing and a picture will be taken when
1640        // focus callback arrives.
1641        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
1642                && !mIsImageCaptureIntent) {
1643            mSnapshotOnIdle = true;
1644            return;
1645        }
1646
1647        mSnapshotOnIdle = false;
1648        mFocusManager.doSnap();
1649    }
1650
1651    @Override
1652    public void installIntentFilter() {
1653    }
1654
1655    @Override
1656    public boolean updateStorageHintOnResume() {
1657        return mFirstTimeInitialized;
1658    }
1659
1660    @Override
1661    public void updateCameraAppView() {
1662    }
1663
1664    @Override
1665    public void onResumeBeforeSuper() {
1666        mPaused = false;
1667    }
1668
1669    @Override
1670    public void onResumeAfterSuper() {
1671        if (mOpenCameraFail || mCameraDisabled) return;
1672
1673        mJpegPictureCallbackTime = 0;
1674        mZoomValue = 0;
1675
1676        // Start the preview if it is not started.
1677        if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
1678            resetExposureCompensation();
1679            mCameraStartUpThread = new CameraStartUpThread();
1680            mCameraStartUpThread.start();
1681        }
1682
1683        // If first time initialization is not finished, put it in the
1684        // message queue.
1685        if (!mFirstTimeInitialized) {
1686            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1687        } else {
1688            initializeSecondTime();
1689        }
1690        keepScreenOnAwhile();
1691
1692        // Dismiss open menu if exists.
1693        PopupManager.getInstance(mActivity).notifyShowPopup(null);
1694    }
1695
1696    void waitCameraStartUpThread() {
1697        try {
1698            if (mCameraStartUpThread != null) {
1699                mCameraStartUpThread.cancel();
1700                mCameraStartUpThread.join();
1701                mCameraStartUpThread = null;
1702                setCameraState(IDLE);
1703            }
1704        } catch (InterruptedException e) {
1705            // ignore
1706        }
1707    }
1708
1709    @Override
1710    public void onPauseBeforeSuper() {
1711        mPaused = true;
1712    }
1713
1714    @Override
1715    public void onPauseAfterSuper() {
1716        // Wait the camera start up thread to finish.
1717        waitCameraStartUpThread();
1718
1719        // When camera is started from secure lock screen for the first time
1720        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
1721        // To reduce the latency, keep the camera for a short time so it does
1722        // not need to be opened again.
1723        if (mCameraDevice != null && mActivity.isSecureCamera()
1724                && ActivityBase.isFirstStartAfterScreenOn()) {
1725            ActivityBase.resetFirstStartAfterScreenOn();
1726            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
1727        }
1728        stopPreview();
1729        // Close the camera now because other activities may need to use it.
1730        closeCamera();
1731        if (mSurfaceTexture != null) {
1732            ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture();
1733            mSurfaceTexture = null;
1734        }
1735        resetScreenOn();
1736
1737        // Clear UI.
1738        collapseCameraControls();
1739        if (mFaceView != null) mFaceView.clear();
1740
1741        if (mFirstTimeInitialized) {
1742            if (mImageSaver != null) {
1743                mImageSaver.finish();
1744                mImageSaver = null;
1745                mImageNamer.finish();
1746                mImageNamer = null;
1747            }
1748        }
1749
1750        if (mLocationManager != null) mLocationManager.recordLocation(false);
1751        updateExposureOnScreenIndicator(0);
1752
1753        // If we are in an image capture intent and has taken
1754        // a picture, we just clear it in onPause.
1755        mJpegImageData = null;
1756
1757        // Remove the messages in the event queue.
1758        mHandler.removeMessages(SETUP_PREVIEW);
1759        mHandler.removeMessages(FIRST_TIME_INIT);
1760        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1761        mHandler.removeMessages(SWITCH_CAMERA);
1762        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
1763        mHandler.removeMessages(CAMERA_OPEN_DONE);
1764        mHandler.removeMessages(START_PREVIEW_DONE);
1765        mHandler.removeMessages(OPEN_CAMERA_FAIL);
1766        mHandler.removeMessages(CAMERA_DISABLED);
1767
1768        mPendingSwitchCameraId = -1;
1769        if (mFocusManager != null) mFocusManager.removeMessages();
1770    }
1771
1772    private void initializeControlByIntent() {
1773        if (mIsImageCaptureIntent) {
1774
1775            mActivity.hideSwitcher();
1776            // Cannot use RotateImageView for "done" and "cancel" button because
1777            // the tablet layout uses RotateLayout, which cannot be cast to
1778            // RotateImageView.
1779            mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
1780            mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
1781
1782            ((View) mReviewCancelButton).setVisibility(View.VISIBLE);
1783
1784            ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() {
1785                @Override
1786                public void onClick(View v) {
1787                    onReviewDoneClicked(v);
1788                }
1789            });
1790            ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() {
1791                @Override
1792                public void onClick(View v) {
1793                    onReviewCancelClicked(v);
1794                }
1795            });
1796
1797            // Not grayed out upon disabled, to make the follow-up fade-out
1798            // effect look smooth. Note that the review done button in tablet
1799            // layout is not a TwoStateImageView.
1800            if (mReviewDoneButton instanceof TwoStateImageView) {
1801                ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
1802            }
1803
1804            setupCaptureParams();
1805        }
1806    }
1807
1808    /**
1809     * The focus manager is the first UI related element to get initialized,
1810     * and it requires the RenderOverlay, so initialize it here
1811     */
1812    private void initializeFocusManager() {
1813        // Create FocusManager object. startPreview needs it.
1814        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
1815        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1816        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
1817        String[] defaultFocusModes = mActivity.getResources().getStringArray(
1818                R.array.pref_camera_focusmode_default_array);
1819        // if mFocusManager not null, reuse it
1820        // otherwise create a new instance
1821        if (mFocusManager != null) {
1822            mFocusManager.removeMessages();
1823        } else {
1824            mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
1825                    mInitialParams, this, mirror,
1826                    mActivity.getMainLooper());
1827        }
1828    }
1829
1830    private void initializeMiscControls() {
1831        // startPreview needs this.
1832        mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
1833        // Set touch focus listener.
1834        mActivity.setSingleTapUpListener(mPreviewFrameLayout);
1835
1836        mOnScreenIndicators = (Rotatable) mRootView.findViewById(R.id.on_screen_indicators);
1837        mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
1838        mPreviewFrameLayout.setOnSizeChangedListener(this);
1839        mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
1840        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
1841            mPreviewSurfaceView =
1842                    (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view);
1843            mPreviewSurfaceView.setVisibility(View.VISIBLE);
1844            mPreviewSurfaceView.getHolder().addCallback(this);
1845        }
1846    }
1847
1848    @Override
1849    public void onConfigurationChanged(Configuration newConfig) {
1850        Log.v(TAG, "onConfigurationChanged");
1851        setDisplayOrientation();
1852
1853        ((ViewGroup) mRootView).removeAllViews();
1854        LayoutInflater inflater = mActivity.getLayoutInflater();
1855        inflater.inflate(R.layout.photo_module, (ViewGroup) mRootView);
1856
1857        // from onCreate()
1858        initializeControlByIntent();
1859
1860        initializeFocusManager();
1861        initializeMiscControls();
1862        loadCameraPreferences();
1863        initializePhotoControl();
1864
1865        // from initializeFirstTime()
1866        mShutterButton = mActivity.getShutterButton();
1867        mShutterButton.setOnShutterButtonListener(this);
1868        initializeZoom();
1869        initOnScreenIndicator();
1870        updateOnScreenIndicators();
1871        if (mFaceView != null) {
1872            mFaceView.clear();
1873            mFaceView.setVisibility(View.VISIBLE);
1874            mFaceView.setDisplayOrientation(mDisplayOrientation);
1875            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1876            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
1877            mFaceView.resume();
1878            mFocusManager.setFaceView(mFaceView);
1879        }
1880        initializeRenderOverlay();
1881    }
1882
1883    @Override
1884    public void onActivityResult(
1885            int requestCode, int resultCode, Intent data) {
1886        switch (requestCode) {
1887            case REQUEST_CROP: {
1888                Intent intent = new Intent();
1889                if (data != null) {
1890                    Bundle extras = data.getExtras();
1891                    if (extras != null) {
1892                        intent.putExtras(extras);
1893                    }
1894                }
1895                mActivity.setResultEx(resultCode, intent);
1896                mActivity.finish();
1897
1898                File path = mActivity.getFileStreamPath(sTempCropFilename);
1899                path.delete();
1900
1901                break;
1902            }
1903        }
1904    }
1905
1906    private boolean canTakePicture() {
1907        return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
1908    }
1909
1910    @Override
1911    public void autoFocus() {
1912        mFocusStartTime = System.currentTimeMillis();
1913        mCameraDevice.autoFocus(mAutoFocusCallback);
1914        setCameraState(FOCUSING);
1915    }
1916
1917    @Override
1918    public void cancelAutoFocus() {
1919        mCameraDevice.cancelAutoFocus();
1920        setCameraState(IDLE);
1921        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1922    }
1923
1924    @Override
1925    public void onMenuClicked() {
1926        if (mPieRenderer != null) {
1927            mPieRenderer.showInCenter();
1928        }
1929    }
1930
1931    // Preview area is touched. Handle touch focus.
1932    @Override
1933    public void onSingleTapUp(View view, int x, int y) {
1934        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1935                || mCameraState == SNAPSHOT_IN_PROGRESS
1936                || mCameraState == SWITCHING_CAMERA
1937                || mCameraState == PREVIEW_STOPPED) {
1938            return;
1939        }
1940
1941        // Do not trigger touch focus if popup window is opened.
1942        if (removeTopLevelPopup()) return;
1943
1944        // Check if metering area or focus area is supported.
1945        if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1946            if (mPieRenderer != null) {
1947                mPieRenderer.setFocus(x,  y, true);
1948            }
1949        } else {
1950            mFocusManager.onSingleTapUp(x, y);
1951        }
1952    }
1953
1954    @Override
1955    public boolean onBackPressed() {
1956        // In image capture mode, back button should:
1957        // 1) if there is any popup, dismiss them, 2) otherwise, get out of image capture
1958        if (mIsImageCaptureIntent) {
1959            if (!removeTopLevelPopup()) {
1960                // no popup to dismiss, cancel image capture
1961                doCancel();
1962            }
1963            return true;
1964        } else if (!isCameraIdle()) {
1965            // ignore backs while we're taking a picture
1966            return true;
1967        } else {
1968            return removeTopLevelPopup();
1969        }
1970    }
1971
1972    @Override
1973    public boolean onKeyDown(int keyCode, KeyEvent event) {
1974        switch (keyCode) {
1975            case KeyEvent.KEYCODE_FOCUS:
1976                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1977                    onShutterButtonFocus(true);
1978                }
1979                return true;
1980            case KeyEvent.KEYCODE_CAMERA:
1981                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1982                    onShutterButtonClick();
1983                }
1984                return true;
1985            case KeyEvent.KEYCODE_DPAD_CENTER:
1986                // If we get a dpad center event without any focused view, move
1987                // the focus to the shutter button and press it.
1988                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1989                    // Start auto-focus immediately to reduce shutter lag. After
1990                    // the shutter button gets the focus, onShutterButtonFocus()
1991                    // will be called again but it is fine.
1992                    if (removeTopLevelPopup()) return true;
1993                    onShutterButtonFocus(true);
1994                    if (mShutterButton.isInTouchMode()) {
1995                        mShutterButton.requestFocusFromTouch();
1996                    } else {
1997                        mShutterButton.requestFocus();
1998                    }
1999                    mShutterButton.setPressed(true);
2000                }
2001                return true;
2002        }
2003        return false;
2004    }
2005
2006    @Override
2007    public boolean onKeyUp(int keyCode, KeyEvent event) {
2008        switch (keyCode) {
2009            case KeyEvent.KEYCODE_FOCUS:
2010                if (mFirstTimeInitialized) {
2011                    onShutterButtonFocus(false);
2012                }
2013                return true;
2014        }
2015        return false;
2016    }
2017
2018    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
2019    private void closeCamera() {
2020        if (mCameraDevice != null) {
2021            mCameraDevice.setZoomChangeListener(null);
2022            if(ApiHelper.HAS_FACE_DETECTION) {
2023                mCameraDevice.setFaceDetectionListener(null);
2024            }
2025            mCameraDevice.setErrorCallback(null);
2026            CameraHolder.instance().release();
2027            mFaceDetectionStarted = false;
2028            mCameraDevice = null;
2029            setCameraState(PREVIEW_STOPPED);
2030            mFocusManager.onCameraReleased();
2031        }
2032    }
2033
2034    private void setDisplayOrientation() {
2035        mDisplayRotation = Util.getDisplayRotation(mActivity);
2036        mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
2037        mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
2038        if (mFaceView != null) {
2039            mFaceView.setDisplayOrientation(mDisplayOrientation);
2040        }
2041        mFocusManager.setDisplayOrientation(mDisplayOrientation);
2042    }
2043
2044    // Only called by UI thread.
2045    private void setupPreview() {
2046        mFocusManager.resetTouchFocus();
2047        startPreview();
2048        setCameraState(IDLE);
2049        startFaceDetection();
2050    }
2051
2052    // This can be called by UI Thread or CameraStartUpThread. So this should
2053    // not modify the views.
2054    private void startPreview() {
2055        mCameraDevice.setErrorCallback(mErrorCallback);
2056
2057        // If we're previewing already, stop the preview first (this will blank
2058        // the screen).
2059        if (mCameraState != PREVIEW_STOPPED) stopPreview();
2060
2061        setDisplayOrientation();
2062
2063        if (!mSnapshotOnIdle) {
2064            // If the focus mode is continuous autofocus, call cancelAutoFocus to
2065            // resume it because it may have been paused by autoFocus call.
2066            if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
2067                mCameraDevice.cancelAutoFocus();
2068            }
2069            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
2070        }
2071        setCameraParameters(UPDATE_PARAM_ALL);
2072
2073        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2074            CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
2075            if (mSurfaceTexture == null) {
2076                Size size = mParameters.getPreviewSize();
2077                if (mCameraDisplayOrientation % 180 == 0) {
2078                    screenNail.setSize(size.width, size.height);
2079                } else {
2080                    screenNail.setSize(size.height, size.width);
2081                }
2082                mActivity.notifyScreenNailChanged();
2083                screenNail.acquireSurfaceTexture();
2084                mSurfaceTexture = screenNail.getSurfaceTexture();
2085            }
2086            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
2087            mCameraDevice.setPreviewTextureAsync((SurfaceTexture) mSurfaceTexture);
2088        } else {
2089            mCameraDevice.setDisplayOrientation(mDisplayOrientation);
2090            mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);
2091        }
2092
2093        Log.v(TAG, "startPreview");
2094        mCameraDevice.startPreviewAsync();
2095
2096        mFocusManager.onPreviewStarted();
2097
2098        if (mSnapshotOnIdle) {
2099            mHandler.post(mDoSnapRunnable);
2100        }
2101    }
2102
2103    private void stopPreview() {
2104        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
2105            Log.v(TAG, "stopPreview");
2106            mCameraDevice.cancelAutoFocus(); // Reset the focus.
2107            mCameraDevice.stopPreview();
2108            mFaceDetectionStarted = false;
2109        }
2110        setCameraState(PREVIEW_STOPPED);
2111        if (mFocusManager != null) mFocusManager.onPreviewStopped();
2112    }
2113
2114    @SuppressWarnings("deprecation")
2115    private void updateCameraParametersInitialize() {
2116        // Reset preview frame rate to the maximum because it may be lowered by
2117        // video camera application.
2118        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
2119        if (frameRates != null) {
2120            Integer max = Collections.max(frameRates);
2121            mParameters.setPreviewFrameRate(max);
2122        }
2123
2124        mParameters.set(Util.RECORDING_HINT, Util.FALSE);
2125
2126        // Disable video stabilization. Convenience methods not available in API
2127        // level <= 14
2128        String vstabSupported = mParameters.get("video-stabilization-supported");
2129        if ("true".equals(vstabSupported)) {
2130            mParameters.set("video-stabilization", "false");
2131        }
2132    }
2133
2134    private void updateCameraParametersZoom() {
2135        // Set zoom.
2136        if (mParameters.isZoomSupported()) {
2137            mParameters.setZoom(mZoomValue);
2138        }
2139    }
2140
2141    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
2142    private void setAutoExposureLockIfSupported() {
2143        if (mAeLockSupported) {
2144            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
2145        }
2146    }
2147
2148    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
2149    private void setAutoWhiteBalanceLockIfSupported() {
2150        if (mAwbLockSupported) {
2151            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2152        }
2153    }
2154
2155    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
2156    private void setFocusAreasIfSupported() {
2157        if (mFocusAreaSupported) {
2158            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
2159        }
2160    }
2161
2162    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
2163    private void setMeteringAreasIfSupported() {
2164        if (mMeteringAreaSupported) {
2165            // Use the same area for focus and metering.
2166            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
2167        }
2168    }
2169
2170    private void updateCameraParametersPreference() {
2171        setAutoExposureLockIfSupported();
2172        setAutoWhiteBalanceLockIfSupported();
2173        setFocusAreasIfSupported();
2174        setMeteringAreasIfSupported();
2175
2176        // Set picture size.
2177        String pictureSize = mPreferences.getString(
2178                CameraSettings.KEY_PICTURE_SIZE, null);
2179        if (pictureSize == null) {
2180            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
2181        } else {
2182            List<Size> supported = mParameters.getSupportedPictureSizes();
2183            CameraSettings.setCameraPictureSize(
2184                    pictureSize, supported, mParameters);
2185        }
2186        Size size = mParameters.getPictureSize();
2187
2188        // Set a preview size that is closest to the viewfinder height and has
2189        // the right aspect ratio.
2190        List<Size> sizes = mParameters.getSupportedPreviewSizes();
2191        Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
2192                (double) size.width / size.height);
2193        Size original = mParameters.getPreviewSize();
2194        if (!original.equals(optimalSize)) {
2195            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
2196
2197            // Zoom related settings will be changed for different preview
2198            // sizes, so set and read the parameters to get latest values
2199            mCameraDevice.setParameters(mParameters);
2200            mParameters = mCameraDevice.getParameters();
2201        }
2202        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
2203
2204        // Since changing scene mode may change supported values, set scene mode
2205        // first. HDR is a scene mode. To promote it in UI, it is stored in a
2206        // separate preference.
2207        String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
2208                mActivity.getString(R.string.pref_camera_hdr_default));
2209        if (mActivity.getString(R.string.setting_on_value).equals(hdr)) {
2210            mSceneMode = Util.SCENE_MODE_HDR;
2211        } else {
2212            mSceneMode = mPreferences.getString(
2213                CameraSettings.KEY_SCENE_MODE,
2214                mActivity.getString(R.string.pref_camera_scenemode_default));
2215        }
2216        if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
2217            if (!mParameters.getSceneMode().equals(mSceneMode)) {
2218                mParameters.setSceneMode(mSceneMode);
2219
2220                // Setting scene mode will change the settings of flash mode,
2221                // white balance, and focus mode. Here we read back the
2222                // parameters, so we can know those settings.
2223                mCameraDevice.setParameters(mParameters);
2224                mParameters = mCameraDevice.getParameters();
2225            }
2226        } else {
2227            mSceneMode = mParameters.getSceneMode();
2228            if (mSceneMode == null) {
2229                mSceneMode = Parameters.SCENE_MODE_AUTO;
2230            }
2231        }
2232
2233        // Set JPEG quality.
2234        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2235                CameraProfile.QUALITY_HIGH);
2236        mParameters.setJpegQuality(jpegQuality);
2237
2238        // For the following settings, we need to check if the settings are
2239        // still supported by latest driver, if not, ignore the settings.
2240
2241        // Set exposure compensation
2242        int value = CameraSettings.readExposure(mPreferences);
2243        int max = mParameters.getMaxExposureCompensation();
2244        int min = mParameters.getMinExposureCompensation();
2245        if (value >= min && value <= max) {
2246            mParameters.setExposureCompensation(value);
2247        } else {
2248            Log.w(TAG, "invalid exposure range: " + value);
2249        }
2250
2251        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
2252            // Set flash mode.
2253            String flashMode = mPreferences.getString(
2254                    CameraSettings.KEY_FLASH_MODE,
2255                    mActivity.getString(R.string.pref_camera_flashmode_default));
2256            List<String> supportedFlash = mParameters.getSupportedFlashModes();
2257            if (Util.isSupported(flashMode, supportedFlash)) {
2258                mParameters.setFlashMode(flashMode);
2259            } else {
2260                flashMode = mParameters.getFlashMode();
2261                if (flashMode == null) {
2262                    flashMode = mActivity.getString(
2263                            R.string.pref_camera_flashmode_no_flash);
2264                }
2265            }
2266
2267            // Set white balance parameter.
2268            String whiteBalance = mPreferences.getString(
2269                    CameraSettings.KEY_WHITE_BALANCE,
2270                    mActivity.getString(R.string.pref_camera_whitebalance_default));
2271            if (Util.isSupported(whiteBalance,
2272                    mParameters.getSupportedWhiteBalance())) {
2273                mParameters.setWhiteBalance(whiteBalance);
2274            } else {
2275                whiteBalance = mParameters.getWhiteBalance();
2276                if (whiteBalance == null) {
2277                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
2278                }
2279            }
2280
2281            // Set focus mode.
2282            mFocusManager.overrideFocusMode(null);
2283            mParameters.setFocusMode(mFocusManager.getFocusMode());
2284        } else {
2285            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
2286        }
2287
2288        if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2289            updateAutoFocusMoveCallback();
2290        }
2291    }
2292
2293    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
2294    private void updateAutoFocusMoveCallback() {
2295        if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
2296            mCameraDevice.setAutoFocusMoveCallback(
2297                (AutoFocusMoveCallback) mAutoFocusMoveCallback);
2298        } else {
2299            mCameraDevice.setAutoFocusMoveCallback(null);
2300        }
2301    }
2302
2303    // We separate the parameters into several subsets, so we can update only
2304    // the subsets actually need updating. The PREFERENCE set needs extra
2305    // locking because the preference can be changed from GLThread as well.
2306    private void setCameraParameters(int updateSet) {
2307        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2308            updateCameraParametersInitialize();
2309        }
2310
2311        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2312            updateCameraParametersZoom();
2313        }
2314
2315        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2316            updateCameraParametersPreference();
2317        }
2318
2319        mCameraDevice.setParameters(mParameters);
2320    }
2321
2322    // If the Camera is idle, update the parameters immediately, otherwise
2323    // accumulate them in mUpdateSet and update later.
2324    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2325        mUpdateSet |= additionalUpdateSet;
2326        if (mCameraDevice == null) {
2327            // We will update all the parameters when we open the device, so
2328            // we don't need to do anything now.
2329            mUpdateSet = 0;
2330            return;
2331        } else if (isCameraIdle()) {
2332            setCameraParameters(mUpdateSet);
2333            updateSceneModeUI();
2334            mUpdateSet = 0;
2335        } else {
2336            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2337                mHandler.sendEmptyMessageDelayed(
2338                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2339            }
2340        }
2341    }
2342
2343    private boolean isCameraIdle() {
2344        return (mCameraState == IDLE) ||
2345                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2346                        && (mCameraState != SWITCHING_CAMERA));
2347    }
2348
2349    private boolean isImageCaptureIntent() {
2350        String action = mActivity.getIntent().getAction();
2351        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2352                || ActivityBase.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2353    }
2354
2355    private void setupCaptureParams() {
2356        Bundle myExtras = mActivity.getIntent().getExtras();
2357        if (myExtras != null) {
2358            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2359            mCropValue = myExtras.getString("crop");
2360        }
2361    }
2362
2363    private void showPostCaptureAlert() {
2364        if (mIsImageCaptureIntent) {
2365            Util.fadeOut(mShutterButton);
2366
2367//            Util.fadeIn(mReviewRetakeButton);
2368            Util.fadeIn((View) mReviewDoneButton);
2369        }
2370    }
2371
2372    private void hidePostCaptureAlert() {
2373        if (mIsImageCaptureIntent) {
2374//            Util.fadeOut(mReviewRetakeButton);
2375            Util.fadeOut((View) mReviewDoneButton);
2376
2377            Util.fadeIn(mShutterButton);
2378        }
2379    }
2380
2381    @Override
2382    public void onSharedPreferenceChanged() {
2383        // ignore the events after "onPause()"
2384        if (mPaused) return;
2385
2386        boolean recordLocation = RecordLocationPreference.get(
2387                mPreferences, mContentResolver);
2388        mLocationManager.recordLocation(recordLocation);
2389
2390        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2391        setPreviewFrameLayoutAspectRatio();
2392        updateOnScreenIndicators();
2393    }
2394
2395    @Override
2396    public void onCameraPickerClicked(int cameraId) {
2397        if (mPaused || mPendingSwitchCameraId != -1) return;
2398
2399        mPendingSwitchCameraId = cameraId;
2400        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2401            Log.v(TAG, "Start to copy texture. cameraId=" + cameraId);
2402            // We need to keep a preview frame for the animation before
2403            // releasing the camera. This will trigger onPreviewTextureCopied.
2404            ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
2405            // Disable all camera controls.
2406            setCameraState(SWITCHING_CAMERA);
2407        } else {
2408            switchCamera();
2409        }
2410    }
2411
2412    private void switchCamera() {
2413        if (mPaused) return;
2414
2415        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
2416        mCameraId = mPendingSwitchCameraId;
2417        mPendingSwitchCameraId = -1;
2418        mPhotoControl.setCameraId(mCameraId);
2419
2420        // from onPause
2421        closeCamera();
2422        collapseCameraControls();
2423        if (mFaceView != null) mFaceView.clear();
2424        if (mFocusManager != null) mFocusManager.removeMessages();
2425
2426        // Restart the camera and initialize the UI. From onCreate.
2427        mPreferences.setLocalId(mActivity, mCameraId);
2428        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
2429        try {
2430            mCameraDevice = Util.openCamera(mActivity, mCameraId);
2431            mParameters = mCameraDevice.getParameters();
2432        } catch (CameraHardwareException e) {
2433            Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
2434            return;
2435        } catch (CameraDisabledException e) {
2436            Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
2437            return;
2438        }
2439        initializeCapabilities();
2440        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
2441        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
2442        mFocusManager.setMirror(mirror);
2443        mFocusManager.setParameters(mInitialParams);
2444        setupPreview();
2445        loadCameraPreferences();
2446        initializePhotoControl();
2447
2448        // from onResume
2449        setOrientationIndicator(mOrientationCompensation, false);
2450        // from initializeFirstTime
2451        initializeZoom();
2452        updateOnScreenIndicators();
2453        showTapToFocusToastIfNeeded();
2454
2455        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2456            // Start switch camera animation. Post a message because
2457            // onFrameAvailable from the old camera may already exist.
2458            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
2459        }
2460    }
2461
2462    @Override
2463    public void onPieOpened(int centerX, int centerY) {
2464        mActivity.cancelActivityTouchHandling();
2465        mActivity.setSwipingEnabled(false);
2466    }
2467
2468    @Override
2469    public void onPieClosed() {
2470        mActivity.setSwipingEnabled(true);
2471    }
2472
2473    // Preview texture has been copied. Now camera can be released and the
2474    // animation can be started.
2475    @Override
2476    public void onPreviewTextureCopied() {
2477        mHandler.sendEmptyMessage(SWITCH_CAMERA);
2478    }
2479
2480    @Override
2481    public void onUserInteraction() {
2482        if (!mActivity.isFinishing()) keepScreenOnAwhile();
2483    }
2484
2485    private void resetScreenOn() {
2486        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2487        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2488    }
2489
2490    private void keepScreenOnAwhile() {
2491        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2492        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2493        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2494    }
2495
2496    @Override
2497    public void onRestorePreferencesClicked() {
2498        if (mPaused) return;
2499        Runnable runnable = new Runnable() {
2500            @Override
2501            public void run() {
2502                restorePreferences();
2503            }
2504        };
2505        mRotateDialog.showAlertDialog(
2506                null,
2507                mActivity.getString(R.string.confirm_restore_message),
2508                mActivity.getString(android.R.string.ok), runnable,
2509                mActivity.getString(android.R.string.cancel), null);
2510    }
2511
2512    private void restorePreferences() {
2513        // Reset the zoom. Zoom value is not stored in preference.
2514        if (mParameters.isZoomSupported()) {
2515            mZoomValue = 0;
2516            setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2517            // TODO: reset zoom
2518        }
2519        dismissPopup(true);
2520        CameraSettings.restorePreferences(mActivity, mPreferences,
2521                mParameters);
2522        mPhotoControl.reloadPreferences();
2523        onSharedPreferenceChanged();
2524    }
2525
2526    @Override
2527    public void onOverriddenPreferencesClicked() {
2528        if (mPaused) return;
2529        if (mNotSelectableToast == null) {
2530            String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
2531            mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
2532        }
2533        mNotSelectableToast.show();
2534    }
2535
2536    private void showTapToFocusToast() {
2537        new RotateTextToast(mActivity, R.string.tap_to_focus, mOrientationCompensation).show();
2538        // Clear the preference.
2539        Editor editor = mPreferences.edit();
2540        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
2541        editor.apply();
2542    }
2543
2544    private void initializeCapabilities() {
2545        mInitialParams = mCameraDevice.getParameters();
2546        mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
2547        mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
2548        mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
2549        mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
2550        mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
2551                Util.FOCUS_MODE_CONTINUOUS_PICTURE);
2552    }
2553
2554    // PreviewFrameLayout size has changed.
2555    @Override
2556    public void onSizeChanged(int width, int height) {
2557        if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
2558    }
2559
2560    void setPreviewFrameLayoutAspectRatio() {
2561        // Set the preview frame aspect ratio according to the picture size.
2562        Size size = mParameters.getPictureSize();
2563        mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
2564    }
2565
2566    @Override
2567    public boolean needsSwitcher() {
2568        return !mIsImageCaptureIntent;
2569    }
2570
2571    public void showPopup(AbstractSettingPopup popup) {
2572        mActivity.hideUI();
2573        mPopup = popup;
2574        // Make sure popup is brought up with the right orientation
2575        mPopup.setOrientation(mOrientationCompensation, false);
2576        mPopup.setVisibility(View.VISIBLE);
2577        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
2578                LayoutParams.WRAP_CONTENT);
2579        lp.gravity = Gravity.CENTER;
2580        ((FrameLayout) mRootView).addView(mPopup, lp);
2581    }
2582
2583    public void dismissPopup(boolean topPopupOnly) {
2584        mActivity.showUI();
2585        if (mPopup != null) {
2586            ((FrameLayout) mRootView).removeView(mPopup);
2587            mPopup = null;
2588        }
2589        mPhotoControl.popupDismissed(topPopupOnly);
2590    }
2591
2592}
2593