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