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