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