Camera.java revision 30055f6830b3d524cfbdfeab6536bb46e390747a
1/*
2 * Copyright (C) 2007 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.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.BroadcastReceiver;
22import android.content.ContentProviderClient;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.SharedPreferences;
28import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
29import android.content.res.Resources;
30import android.graphics.Bitmap;
31import android.graphics.BitmapFactory;
32import android.hardware.Camera.Parameters;
33import android.hardware.Camera.PictureCallback;
34import android.hardware.Camera.Size;
35import android.location.Location;
36import android.location.LocationManager;
37import android.location.LocationProvider;
38import android.media.AudioManager;
39import android.media.ToneGenerator;
40import android.net.Uri;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Debug;
44import android.os.Environment;
45import android.os.Handler;
46import android.os.Message;
47import android.os.SystemClock;
48import android.os.SystemProperties;
49import android.preference.PreferenceManager;
50import android.provider.MediaStore;
51import android.util.AttributeSet;
52import android.util.Log;
53import android.view.Display;
54import android.view.GestureDetector;
55import android.view.KeyEvent;
56import android.view.LayoutInflater;
57import android.view.Menu;
58import android.view.MenuItem;
59import android.view.MotionEvent;
60import android.view.OrientationEventListener;
61import android.view.SurfaceHolder;
62import android.view.SurfaceView;
63import android.view.View;
64import android.view.ViewGroup;
65import android.view.Window;
66import android.view.WindowManager;
67import android.view.MenuItem.OnMenuItemClickListener;
68import android.widget.ImageView;
69import android.widget.ZoomButtonsController;
70
71import com.android.camera.gallery.IImage;
72import com.android.camera.gallery.IImageList;
73
74import java.io.File;
75import java.io.FileNotFoundException;
76import java.io.FileOutputStream;
77import java.io.IOException;
78import java.io.OutputStream;
79import java.text.SimpleDateFormat;
80import java.util.ArrayList;
81import java.util.Collections;
82import java.util.Date;
83import java.util.List;
84
85/** The Camera activity which can preview and take pictures. */
86public class Camera extends NoSearchActivity implements View.OnClickListener,
87        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
88        Switcher.OnSwitchListener, OnScreenSettings.OnVisibilityChangedListener,
89        OnSharedPreferenceChangeListener {
90
91    private static final String TAG = "camera";
92
93    // This value must be as same as the item value of the string array
94    // "flash_mode" in file "res/values/arrays.xml".
95    private static final String NO_FLASH_MODE = "no_flash";
96    private static final int CROP_MSG = 1;
97    private static final int FIRST_TIME_INIT = 2;
98    private static final int RESTART_PREVIEW = 3;
99    private static final int CLEAR_SCREEN_DELAY = 4;
100
101    private static final String GPS_MODE_ON = "on";
102    private static final String GPS_MODE_OFF = "off";
103
104    private static final int SCREEN_DELAY = 2 * 60 * 1000;
105    private static final int FOCUS_BEEP_VOLUME = 100;
106
107    private static final String SCENE_MODE_ON = "on";
108    private static final String SCENE_MODE_OFF = "off";
109
110    private boolean mZooming = false;
111    private boolean mSmoothZoomSupported = false;
112    private int mZoomValue;  // The current zoom value.
113    private int mZoomMax;
114
115    private Parameters mParameters;
116    private Parameters mInitialParams;
117
118    private OrientationEventListener mOrientationListener;
119    private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
120    private SharedPreferences mPreferences;
121
122    private static final int IDLE = 1;
123    private static final int SNAPSHOT_IN_PROGRESS = 2;
124
125    private static final boolean SWITCH_CAMERA = true;
126    private static final boolean SWITCH_VIDEO = false;
127
128    private int mStatus = IDLE;
129    private static final String sTempCropFilename = "crop-temp";
130
131    private android.hardware.Camera mCameraDevice;
132    private ContentProviderClient mMediaProviderClient;
133    private SurfaceView mSurfaceView;
134    private SurfaceHolder mSurfaceHolder = null;
135    private ShutterButton mShutterButton;
136    private FocusRectangle mFocusRectangle;
137    private IconIndicator mGpsIndicator;
138    private IconIndicator mFlashIndicator;
139    private IconIndicator mFocusIndicator;
140    private IconIndicator mWhitebalanceIndicator;
141    private IconIndicator mSceneModeIndicator;
142    private ToneGenerator mFocusToneGenerator;
143    private ZoomButtonsController mZoomButtons;
144    private GestureDetector mGestureDetector;
145    private Switcher mSwitcher;
146    private boolean mStartPreviewFail = false;
147
148    // mPostCaptureAlert, mLastPictureButton, mThumbController
149    // are non-null only if isImageCaptureIntent() is true.
150    private ImageView mLastPictureButton;
151    private ThumbnailController mThumbController;
152
153    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
154    private String mCropValue;
155    private Uri mSaveUri;
156
157    private ImageCapture mImageCapture = null;
158
159    private boolean mPreviewing;
160    private boolean mPausing;
161    private boolean mFirstTimeInitialized;
162    private boolean mIsImageCaptureIntent;
163    private boolean mRecordLocation;
164
165    private static final int FOCUS_NOT_STARTED = 0;
166    private static final int FOCUSING = 1;
167    private static final int FOCUSING_SNAP_ON_FINISH = 2;
168    private static final int FOCUS_SUCCESS = 3;
169    private static final int FOCUS_FAIL = 4;
170    private int mFocusState = FOCUS_NOT_STARTED;
171
172    private ContentResolver mContentResolver;
173    private boolean mDidRegister = false;
174
175    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
176
177    private LocationManager mLocationManager = null;
178
179    // Use OneShotPreviewCallback to measure the time between
180    // JpegPictureCallback and preview.
181    private final OneShotPreviewCallback mOneShotPreviewCallback =
182            new OneShotPreviewCallback();
183    private final ShutterCallback mShutterCallback = new ShutterCallback();
184    private final PostViewPictureCallback mPostViewPictureCallback =
185            new PostViewPictureCallback();
186    private final RawPictureCallback mRawPictureCallback =
187            new RawPictureCallback();
188    private final AutoFocusCallback mAutoFocusCallback =
189            new AutoFocusCallback();
190    private final ZoomCallback mZoomCallback = new ZoomCallback();
191    // Use the ErrorCallback to capture the crash count
192    // on the mediaserver
193    private final ErrorCallback mErrorCallback = new ErrorCallback();
194
195    private long mFocusStartTime;
196    private long mFocusCallbackTime;
197    private long mCaptureStartTime;
198    private long mShutterCallbackTime;
199    private long mPostViewPictureCallbackTime;
200    private long mRawPictureCallbackTime;
201    private long mJpegPictureCallbackTime;
202    private int mPicturesRemaining;
203
204    // These latency time are for the CameraLatency test.
205    public long mAutoFocusTime;
206    public long mShutterLag;
207    public long mShutterToPictureDisplayedTime;
208    public long mPictureDisplayedToJpegCallbackTime;
209    public long mJpegCallbackToFirstFrameTime;
210
211    // Add for test
212    public static boolean mMediaServerDied = false;
213
214    // Focus mode. Options are pref_camera_focusmode_entryvalues.
215    private String mFocusMode;
216
217    private final Handler mHandler = new MainHandler();
218    private OnScreenSettings mSettings;
219
220    /**
221     * This Handler is used to post message back onto the main thread of the
222     * application
223     */
224    private class MainHandler extends Handler {
225        @Override
226        public void handleMessage(Message msg) {
227            switch (msg.what) {
228                case RESTART_PREVIEW: {
229                    restartPreview();
230                    break;
231                }
232
233                case CLEAR_SCREEN_DELAY: {
234                    getWindow().clearFlags(
235                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
236                    break;
237                }
238
239                case FIRST_TIME_INIT: {
240                    initializeFirstTime();
241                    break;
242                }
243            }
244        }
245    }
246
247    private void keepMediaProviderInstance() {
248        // We want to keep a reference to MediaProvider in camera's lifecycle.
249        // TODO: Utilize mMediaProviderClient instance to replace
250        // ContentResolver calls.
251        if (mMediaProviderClient == null) {
252            mMediaProviderClient = getContentResolver()
253                    .acquireContentProviderClient(MediaStore.AUTHORITY);
254        }
255    }
256
257    // Snapshots can only be taken after this is called. It should be called
258    // once only. We could have done these things in onCreate() but we want to
259    // make preview screen appear as soon as possible.
260    private void initializeFirstTime() {
261        if (mFirstTimeInitialized) return;
262
263        // Create orientation listenter. This should be done first because it
264        // takes some time to get first orientation.
265        mOrientationListener =
266                new OrientationEventListener(Camera.this) {
267            @Override
268            public void onOrientationChanged(int orientation) {
269                // We keep the last known orientation. So if the user
270                // first orient the camera then point the camera to
271                // floor/sky, we still have the correct orientation.
272                if (orientation != ORIENTATION_UNKNOWN) {
273                    mLastOrientation = orientation;
274                }
275            }
276        };
277        mOrientationListener.enable();
278
279        // Initialize location sevice.
280        mLocationManager = (LocationManager)
281                getSystemService(Context.LOCATION_SERVICE);
282        readPreference();
283        if (mRecordLocation) startReceivingLocationUpdates();
284
285        keepMediaProviderInstance();
286        checkStorage();
287
288        // Initialize last picture button.
289        mContentResolver = getContentResolver();
290        if (!mIsImageCaptureIntent)  {
291            findViewById(R.id.camera_switch).setOnClickListener(this);
292            mLastPictureButton =
293                    (ImageView) findViewById(R.id.review_thumbnail);
294            mLastPictureButton.setOnClickListener(this);
295            mThumbController = new ThumbnailController(
296                    getResources(), mLastPictureButton, mContentResolver);
297            mThumbController.loadData(ImageManager.getLastImageThumbPath());
298            // Update last image thumbnail.
299            updateThumbnailButton();
300        }
301
302        // Initialize shutter button.
303        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
304        mShutterButton.setOnShutterButtonListener(this);
305        mShutterButton.setVisibility(View.VISIBLE);
306
307        mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
308        updateFocusIndicator();
309
310        // Initialize GPS indicator.
311        mGpsIndicator = (IconIndicator) findViewById(R.id.gps_icon);
312
313        ImageManager.ensureOSXCompatibleFolder();
314
315        installIntentFilter();
316
317        initializeFocusTone();
318
319        initializeZoom();
320
321        mFirstTimeInitialized = true;
322    }
323
324    private void updateThumbnailButton() {
325        // Update last image if URI is invalid and the storage is ready.
326        if (!mThumbController.isUriValid() && mPicturesRemaining >= 0) {
327            updateLastImage();
328        }
329        mThumbController.updateDisplayIfNeeded();
330    }
331
332    // If the activity is paused and resumed, this method will be called in
333    // onResume.
334    private void initializeSecondTime() {
335        // Start orientation listener as soon as possible because it takes
336        // some time to get first orientation.
337        mOrientationListener.enable();
338
339        // Start location update if needed.
340        readPreference();
341        if (mRecordLocation) startReceivingLocationUpdates();
342
343        installIntentFilter();
344
345        initializeFocusTone();
346
347        keepMediaProviderInstance();
348        checkStorage();
349
350        if (mZoomButtons != null) {
351            mCameraDevice.setZoomCallback(mZoomCallback);
352        }
353
354        if (!mIsImageCaptureIntent) {
355            updateThumbnailButton();
356        }
357    }
358
359    private void initializeZoom() {
360        if (!mParameters.isZoomSupported()) return;
361
362        mZoomMax = mParameters.getMaxZoom();
363        Log.v(TAG, "Max zoom=" + mZoomMax);
364        mSmoothZoomSupported = mParameters.isSmoothZoomSupported();
365        Log.v(TAG, "Smooth zoom supported=" + mSmoothZoomSupported);
366
367        mGestureDetector = new GestureDetector(this, new ZoomGestureListener());
368        mCameraDevice.setZoomCallback(mZoomCallback);
369        mZoomButtons = new ZoomButtonsController(mSurfaceView);
370        mZoomButtons.setAutoDismissed(true);
371        mZoomButtons.setZoomSpeed(100);
372        mZoomButtons.setOnZoomListener(
373                new ZoomButtonsController.OnZoomListener() {
374            public void onVisibilityChanged(boolean visible) {
375                if (visible) {
376                    updateZoomButtonsEnabled();
377                }
378            }
379
380            public void onZoom(boolean zoomIn) {
381                if (isZooming()) return;
382
383                if (zoomIn) {
384                    if (mZoomValue < mZoomMax) {
385                        if (mSmoothZoomSupported) {
386                            mCameraDevice.startSmoothZoom(mZoomValue + 1);
387                            mZooming = true;
388                        } else {
389                            mParameters.setZoom(++mZoomValue);
390                            mCameraDevice.setParameters(mParameters);
391                            updateZoomButtonsEnabled();
392                        }
393                    }
394                } else {
395                    if (mZoomValue > 0) {
396                        if (mSmoothZoomSupported) {
397                            mCameraDevice.startSmoothZoom(mZoomValue - 1);
398                            mZooming = true;
399                        } else {
400                            mParameters.setZoom(--mZoomValue);
401                            mCameraDevice.setParameters(mParameters);
402                            updateZoomButtonsEnabled();
403                        }
404                    }
405                }
406            }
407        });
408    }
409
410    public void onVisibilityChanged(boolean visible) {
411        // When the on-screen setting is not displayed, we show the gripper.
412        // When the on-screen setting is displayed, we hide the gripper.
413        int reverseVisibility = visible ? View.INVISIBLE : View.VISIBLE;
414        findViewById(R.id.btn_gripper).setVisibility(reverseVisibility);
415        findViewById(R.id.indicator_bar).setVisibility(reverseVisibility);
416
417        if (visible) {
418            mPreferences.registerOnSharedPreferenceChangeListener(this);
419        } else {
420            mPreferences.unregisterOnSharedPreferenceChangeListener(this);
421        }
422    }
423
424    private boolean isZooming() {
425        Log.v(TAG, "mZooming=" + mZooming);
426        return mZooming;
427    }
428
429    private void updateZoomButtonsEnabled() {
430        mZoomButtons.setZoomInEnabled(mZoomValue < mZoomMax);
431        mZoomButtons.setZoomOutEnabled(mZoomValue > 0);
432    }
433
434    private class ZoomGestureListener extends
435            GestureDetector.SimpleOnGestureListener {
436        @Override
437        public boolean onDown(MotionEvent e) {
438            // Show zoom buttons only when preview is started and snapshot
439            // is not in progress. mZoomButtons may be null if it is not
440            // initialized.
441            if (!mPausing && isCameraIdle() && mPreviewing
442                    && mZoomButtons != null) {
443                mZoomButtons.setVisible(true);
444            }
445            return true;
446        }
447
448        @Override
449        public boolean onDoubleTap(MotionEvent e) {
450            // Perform zoom only when preview is started and snapshot is not in
451            // progress.
452            if (mPausing || !isCameraIdle() || !mPreviewing
453                    || mZoomButtons == null || isZooming()) {
454                return false;
455            }
456
457            if (mZoomValue < mZoomMax) {
458                // Zoom in to the maximum.
459                while (mZoomValue < mZoomMax) {
460                    mParameters.setZoom(++mZoomValue);
461                    mCameraDevice.setParameters(mParameters);
462                    // Wait for a while so we are not changing zoom too fast.
463                    try {
464                        Thread.sleep(5);
465                    } catch (InterruptedException ex) {
466                    }
467                }
468            } else {
469                // Zoom out to the minimum.
470                while (mZoomValue > 0) {
471                    mParameters.setZoom(--mZoomValue);
472                    mCameraDevice.setParameters(mParameters);
473                    // Wait for a while so we are not changing zoom too fast.
474                    try {
475                        Thread.sleep(5);
476                    } catch (InterruptedException ex) {
477                    }
478                }
479            }
480            updateZoomButtonsEnabled();
481            return true;
482        }
483    }
484
485    @Override
486    public boolean dispatchTouchEvent(MotionEvent m) {
487        if (!super.dispatchTouchEvent(m) && mGestureDetector != null) {
488            return mGestureDetector.onTouchEvent(m);
489        }
490        return true;
491    }
492
493    LocationListener [] mLocationListeners = new LocationListener[] {
494            new LocationListener(LocationManager.GPS_PROVIDER),
495            new LocationListener(LocationManager.NETWORK_PROVIDER)
496    };
497
498    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
499        @Override
500        public void onReceive(Context context, Intent intent) {
501            String action = intent.getAction();
502            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
503                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
504                    || action.equals(Intent.ACTION_MEDIA_CHECKING)
505                    || action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
506                checkStorage();
507            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
508                checkStorage();
509                if (!mIsImageCaptureIntent)  {
510                    updateThumbnailButton();
511                }
512            }
513        }
514    };
515
516    private class LocationListener
517            implements android.location.LocationListener {
518        Location mLastLocation;
519        boolean mValid = false;
520        String mProvider;
521
522        public LocationListener(String provider) {
523            mProvider = provider;
524            mLastLocation = new Location(mProvider);
525        }
526
527        public void onLocationChanged(Location newLocation) {
528            if (newLocation.getLatitude() == 0.0
529                    && newLocation.getLongitude() == 0.0) {
530                // Hack to filter out 0.0,0.0 locations
531                return;
532            }
533            // If GPS is available before start camera, we won't get status
534            // update so update GPS indicator when we receive data.
535            if (mRecordLocation
536                    && LocationManager.GPS_PROVIDER.equals(mProvider)) {
537                mGpsIndicator.setMode(GPS_MODE_ON);
538            }
539            mLastLocation.set(newLocation);
540            mValid = true;
541        }
542
543        public void onProviderEnabled(String provider) {
544        }
545
546        public void onProviderDisabled(String provider) {
547            mValid = false;
548        }
549
550        public void onStatusChanged(
551                String provider, int status, Bundle extras) {
552            switch(status) {
553                case LocationProvider.OUT_OF_SERVICE:
554                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
555                    mValid = false;
556                    if (mRecordLocation &&
557                            LocationManager.GPS_PROVIDER.equals(provider)) {
558                        mGpsIndicator.setMode(GPS_MODE_OFF);
559                    }
560                    break;
561                }
562            }
563        }
564
565        public Location current() {
566            return mValid ? mLastLocation : null;
567        }
568    }
569
570    private final class OneShotPreviewCallback
571            implements android.hardware.Camera.PreviewCallback {
572        public void onPreviewFrame(byte[] data,
573                                   android.hardware.Camera camera) {
574            long now = System.currentTimeMillis();
575            if (mJpegPictureCallbackTime != 0) {
576                mJpegCallbackToFirstFrameTime = now - mJpegPictureCallbackTime;
577                Log.v(TAG, "mJpegCallbackToFirstFrameTime = "
578                        + mJpegCallbackToFirstFrameTime + "ms");
579                mJpegPictureCallbackTime = 0;
580            } else {
581                Log.v(TAG, "Got first frame");
582            }
583        }
584    }
585
586    private final class ShutterCallback
587            implements android.hardware.Camera.ShutterCallback {
588        public void onShutter() {
589            mShutterCallbackTime = System.currentTimeMillis();
590            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
591            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
592            clearFocusState();
593        }
594    }
595
596    private final class PostViewPictureCallback implements PictureCallback {
597        public void onPictureTaken(
598                byte [] data, android.hardware.Camera camera) {
599            mPostViewPictureCallbackTime = System.currentTimeMillis();
600            Log.v(TAG, "mShutterToPostViewCallbackTime = "
601                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
602                    + "ms");
603        }
604    }
605
606    private final class RawPictureCallback implements PictureCallback {
607        public void onPictureTaken(
608                byte [] rawData, android.hardware.Camera camera) {
609            mRawPictureCallbackTime = System.currentTimeMillis();
610            Log.v(TAG, "mShutterToRawCallbackTime = "
611                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
612        }
613    }
614
615    private final class JpegPictureCallback implements PictureCallback {
616        Location mLocation;
617
618        public JpegPictureCallback(Location loc) {
619            mLocation = loc;
620        }
621
622        public void onPictureTaken(
623                final byte [] jpegData, final android.hardware.Camera camera) {
624            if (mPausing) {
625                return;
626            }
627
628            mJpegPictureCallbackTime = System.currentTimeMillis();
629            // If postview callback has arrived, the captured image is displayed
630            // in postview callback. If not, the captured image is displayed in
631            // raw picture callback.
632            if (mPostViewPictureCallbackTime != 0) {
633                mShutterToPictureDisplayedTime =
634                        mPostViewPictureCallbackTime - mShutterCallbackTime;
635                mPictureDisplayedToJpegCallbackTime =
636                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
637            } else {
638                mShutterToPictureDisplayedTime =
639                        mRawPictureCallbackTime - mShutterCallbackTime;
640                mPictureDisplayedToJpegCallbackTime =
641                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
642            }
643            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
644                    + mPictureDisplayedToJpegCallbackTime + "ms");
645
646            if (!mIsImageCaptureIntent) {
647                // We want to show the taken picture for a while, so we wait
648                // for at least 1.2 second before restarting the preview.
649                long delay = 1200 - mPictureDisplayedToJpegCallbackTime;
650                if (delay < 0) {
651                    restartPreview();
652                } else {
653                    mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, delay);
654                }
655            }
656            mImageCapture.storeImage(jpegData, camera, mLocation);
657
658            // Calculate this in advance of each shot so we don't add to shutter
659            // latency. It's true that someone else could write to the SD card in
660            // the mean time and fill it, but that could have happened between the
661            // shutter press and saving the JPEG too.
662            calculatePicturesRemaining();
663        }
664    }
665
666    private final class AutoFocusCallback
667            implements android.hardware.Camera.AutoFocusCallback {
668        public void onAutoFocus(
669                boolean focused, android.hardware.Camera camera) {
670            mFocusCallbackTime = System.currentTimeMillis();
671            mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
672            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
673            if (mFocusState == FOCUSING_SNAP_ON_FINISH) {
674                // Take the picture no matter focus succeeds or fails. No need
675                // to play the AF sound if we're about to play the shutter
676                // sound.
677                if (focused) {
678                    mFocusState = FOCUS_SUCCESS;
679                } else {
680                    mFocusState = FOCUS_FAIL;
681                }
682                mImageCapture.onSnap();
683            } else if (mFocusState == FOCUSING) {
684                // User is half-pressing the focus key. Play the focus tone.
685                // Do not take the picture now.
686                ToneGenerator tg = mFocusToneGenerator;
687                if (tg != null) {
688                    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
689                }
690                if (focused) {
691                    mFocusState = FOCUS_SUCCESS;
692                } else {
693                    mFocusState = FOCUS_FAIL;
694                }
695            } else if (mFocusState == FOCUS_NOT_STARTED) {
696                // User has released the focus key before focus completes.
697                // Do nothing.
698            }
699            updateFocusIndicator();
700        }
701    }
702
703    private static final class ErrorCallback
704        implements android.hardware.Camera.ErrorCallback {
705        public void onError(int error, android.hardware.Camera camera) {
706            if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
707                 mMediaServerDied = true;
708                 Log.v(TAG, "media server died");
709            }
710        }
711    }
712
713    private final class ZoomCallback
714            implements android.hardware.Camera.ZoomCallback {
715        public void onZoomUpdate(int zoomValue, boolean stopped,
716                                 android.hardware.Camera camera) {
717            Log.v(TAG, "ZoomCallback: zoom value=" + zoomValue + ". stopped="
718                    + stopped);
719            mZoomValue = zoomValue;
720            // Keep mParameters up to date. We do not getParameter again in
721            // takePicture. If we do not do this, wrong zoom value will be set.
722            mParameters.setZoom(zoomValue);
723            // We only care if the zoom is stopped. mZooming is set to true when
724            // we start smooth zoom.
725            if (stopped) mZooming = false;
726            updateZoomButtonsEnabled();
727        }
728    }
729
730    private class ImageCapture {
731
732        private boolean mCancel = false;
733
734        private Uri mLastContentUri;
735
736        byte[] mCaptureOnlyData;
737
738        // Returns the rotation degree in the jpeg header.
739        private int storeImage(byte[] data, Location loc) {
740            try {
741                long dateTaken = System.currentTimeMillis();
742                String title = createName(dateTaken);
743                String filename = title + ".jpg";
744                int[] degree = new int[1];
745                mLastContentUri = ImageManager.addImage(
746                        mContentResolver,
747                        title,
748                        dateTaken,
749                        loc, // location from gps/network
750                        ImageManager.CAMERA_IMAGE_BUCKET_NAME, filename,
751                        null, data,
752                        degree);
753                if (mLastContentUri == null) {
754                    // this means we got an error
755                    mCancel = true;
756                }
757                if (!mCancel) {
758                    ImageManager.setImageSize(mContentResolver, mLastContentUri,
759                            new File(ImageManager.CAMERA_IMAGE_BUCKET_NAME,
760                            filename).length());
761                }
762                return degree[0];
763            } catch (Exception ex) {
764                Log.e(TAG, "Exception while compressing image.", ex);
765                return 0;
766            }
767        }
768
769        public void storeImage(final byte[] data,
770                android.hardware.Camera camera, Location loc) {
771            if (!mIsImageCaptureIntent) {
772                int degree = storeImage(data, loc);
773                sendBroadcast(new Intent(
774                        "com.android.camera.NEW_PICTURE", mLastContentUri));
775                setLastPictureThumb(data, degree,
776                        mImageCapture.getLastCaptureUri());
777                mThumbController.updateDisplayIfNeeded();
778            } else {
779                mCaptureOnlyData = data;
780                showPostCaptureAlert();
781            }
782        }
783
784        /**
785         * Initiate the capture of an image.
786         */
787        public void initiate() {
788            if (mCameraDevice == null) {
789                return;
790            }
791
792            mCancel = false;
793
794            capture();
795        }
796
797        public Uri getLastCaptureUri() {
798            return mLastContentUri;
799        }
800
801        public byte[] getLastCaptureData() {
802            return mCaptureOnlyData;
803        }
804
805        private void capture() {
806            mCaptureOnlyData = null;
807
808            // Set rotation.
809            int orientation = mLastOrientation;
810            if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
811                orientation += 90;
812            }
813            orientation = ImageManager.roundOrientation(orientation);
814            Log.v(TAG, "mLastOrientation = " + mLastOrientation
815                    + ", orientation = " + orientation);
816            mParameters.setRotation(orientation);
817
818            // Clear previous GPS location from the parameters.
819            mParameters.removeGpsData();
820
821            // Set GPS location.
822            Location loc = mRecordLocation ? getCurrentLocation() : null;
823            if (loc != null) {
824                double lat = loc.getLatitude();
825                double lon = loc.getLongitude();
826                boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
827
828                if (hasLatLon) {
829                    mParameters.setGpsLatitude(lat);
830                    mParameters.setGpsLongitude(lon);
831                    if (loc.hasAltitude()) {
832                        mParameters.setGpsAltitude(loc.getAltitude());
833                    } else {
834                        // for NETWORK_PROVIDER location provider, we may have
835                        // no altitude information, but the driver needs it, so
836                        // we fake one.
837                        mParameters.setGpsAltitude(0);
838                    }
839                    if (loc.getTime() != 0) {
840                        // Location.getTime() is UTC in milliseconds.
841                        // gps-timestamp is UTC in seconds.
842                        long utcTimeSeconds = loc.getTime() / 1000;
843                        mParameters.setGpsTimestamp(utcTimeSeconds);
844                    }
845                } else {
846                    loc = null;
847                }
848            }
849
850            mCameraDevice.setParameters(mParameters);
851
852            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
853                    mPostViewPictureCallback, new JpegPictureCallback(loc));
854            mPreviewing = false;
855        }
856
857        public void onSnap() {
858            // If we are already in the middle of taking a snapshot then ignore.
859            if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS) {
860                return;
861            }
862            mCaptureStartTime = System.currentTimeMillis();
863            mPostViewPictureCallbackTime = 0;
864
865            // Don't check the filesystem here, we can't afford the latency.
866            // Instead, check the cached value which was calculated when the
867            // preview was restarted.
868            if (mPicturesRemaining < 1) {
869                updateStorageHint(mPicturesRemaining);
870                return;
871            }
872
873            mStatus = SNAPSHOT_IN_PROGRESS;
874
875            mImageCapture.initiate();
876        }
877
878        private void clearLastData() {
879            mCaptureOnlyData = null;
880        }
881    }
882
883    private boolean saveDataToFile(String filePath, byte[] data) {
884        FileOutputStream f = null;
885        try {
886            f = new FileOutputStream(filePath);
887            f.write(data);
888        } catch (IOException e) {
889            return false;
890        } finally {
891            MenuHelper.closeSilently(f);
892        }
893        return true;
894    }
895
896    private void setLastPictureThumb(byte[] data, int degree, Uri uri) {
897        BitmapFactory.Options options = new BitmapFactory.Options();
898        options.inSampleSize = 16;
899        Bitmap lastPictureThumb =
900                BitmapFactory.decodeByteArray(data, 0, data.length, options);
901        lastPictureThumb = Util.rotate(lastPictureThumb, degree);
902        mThumbController.setData(uri, lastPictureThumb);
903    }
904
905    private String createName(long dateTaken) {
906        Date date = new Date(dateTaken);
907        SimpleDateFormat dateFormat = new SimpleDateFormat(
908                getString(R.string.image_file_name_format));
909
910        return dateFormat.format(date);
911    }
912
913    @Override
914    public void onCreate(Bundle icicle) {
915        super.onCreate(icicle);
916
917        Window win = getWindow();
918        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
919        setContentView(R.layout.camera);
920        mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview);
921
922        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
923        CameraSettings.upgradePreferences(mPreferences);
924
925        /*
926         * To reduce startup time, we start the preview in another thread.
927         * We make sure the preview is started at the end of onCreate.
928         */
929        Thread startPreviewThread = new Thread(new Runnable() {
930            public void run() {
931                try {
932                    mStartPreviewFail = false;
933                    startPreview();
934                } catch (CameraHardwareException e) {
935                    // In eng build, we throw the exception so that test tool
936                    // can detect it and report it
937                    if ("eng".equals(Build.TYPE)) {
938                        throw new RuntimeException(e);
939                    }
940                    mStartPreviewFail = true;
941                }
942            }
943        });
944        startPreviewThread.start();
945
946        // don't set mSurfaceHolder here. We have it set ONLY within
947        // surfaceChanged / surfaceDestroyed, other parts of the code
948        // assume that when it is set, the surface is also set.
949        SurfaceHolder holder = mSurfaceView.getHolder();
950        holder.addCallback(this);
951        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
952
953        mIsImageCaptureIntent = isImageCaptureIntent();
954        if (mIsImageCaptureIntent) {
955            setupCaptureParams();
956        }
957
958        LayoutInflater inflater = getLayoutInflater();
959
960        ViewGroup rootView = (ViewGroup) findViewById(R.id.camera);
961        if (mIsImageCaptureIntent) {
962            View controlBar = inflater.inflate(
963                    R.layout.attach_camera_control, rootView);
964            controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this);
965            controlBar.findViewById(R.id.btn_retake).setOnClickListener(this);
966            controlBar.findViewById(R.id.btn_done).setOnClickListener(this);
967        } else {
968            inflater.inflate(R.layout.camera_control, rootView);
969            mSwitcher = ((Switcher) findViewById(R.id.camera_switch));
970            mSwitcher.setOnSwitchListener(this);
971            mSwitcher.addTouchView(findViewById(R.id.camera_switch_set));
972        }
973        findViewById(R.id.btn_gripper)
974                .setOnTouchListener(new GripperTouchListener());
975
976        mFlashIndicator = (IconIndicator) findViewById(R.id.flash_icon);
977        mFocusIndicator = (IconIndicator) findViewById(R.id.focus_icon);
978        mSceneModeIndicator = (IconIndicator) findViewById(R.id.scenemode_icon);
979        mWhitebalanceIndicator =
980                (IconIndicator) findViewById(R.id.whitebalance_icon);
981
982        // Make sure preview is started.
983        try {
984            startPreviewThread.join();
985            if (mStartPreviewFail) {
986                showCameraErrorAndFinish();
987                return;
988            }
989        } catch (InterruptedException ex) {
990            // ignore
991        }
992        removeUnsupportedIndicators();
993    }
994
995    private void removeUnsupportedIndicators() {
996        if (mParameters.getSupportedFocusModes() == null) {
997            mFocusIndicator.setVisibility(View.GONE);
998        }
999
1000        if (mParameters.getSupportedWhiteBalance() == null) {
1001            mWhitebalanceIndicator.setVisibility(View.GONE);
1002        }
1003
1004        if (mParameters.getSupportedFlashModes() == null) {
1005            mFlashIndicator.setVisibility(View.GONE);
1006        }
1007
1008        if (mParameters.getSupportedSceneModes() == null) {
1009            mSceneModeIndicator.setVisibility(View.GONE);
1010        }
1011    }
1012
1013    private class GripperTouchListener implements View.OnTouchListener {
1014        public boolean onTouch(View view, MotionEvent event) {
1015            switch (event.getAction()) {
1016                case MotionEvent.ACTION_DOWN:
1017                    return true;
1018                case MotionEvent.ACTION_UP:
1019                    showOnScreenSettings();
1020                    return true;
1021            }
1022            return false;
1023        }
1024    }
1025
1026    @Override
1027    public void onStart() {
1028        super.onStart();
1029        if (!mIsImageCaptureIntent) {
1030            mSwitcher.setSwitch(SWITCH_CAMERA);
1031        }
1032    }
1033
1034    @Override
1035    public void onStop() {
1036        super.onStop();
1037        if (mMediaProviderClient != null) {
1038            mMediaProviderClient.release();
1039            mMediaProviderClient = null;
1040        }
1041    }
1042
1043    private void checkStorage() {
1044        if (ImageManager.isMediaScannerScanning(getContentResolver())) {
1045            mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR;
1046        } else {
1047            calculatePicturesRemaining();
1048        }
1049        updateStorageHint(mPicturesRemaining);
1050    }
1051
1052
1053    private void showOnScreenSettings() {
1054        if (mSettings == null) {
1055            mSettings = new OnScreenSettings(
1056                    findViewById(R.id.camera_preview));
1057            CameraSettings helper =
1058                    new CameraSettings(this, mInitialParams);
1059            mSettings.setPreferenceScreen(helper
1060                    .getPreferenceScreen(R.xml.camera_preferences));
1061            mSettings.setOnVisibilityChangedListener(this);
1062
1063            String sceneMode = mParameters.getSceneMode();
1064            if (sceneMode == null
1065                    || Parameters.SCENE_MODE_AUTO.equals(sceneMode)) {
1066                // If scene mode is auto, cancel override in settings
1067                mSettings.overrideSettings(CameraSettings.KEY_FLASH_MODE, null);
1068                mSettings.overrideSettings(CameraSettings.KEY_FOCUS_MODE, null);
1069                mSettings.overrideSettings(
1070                        CameraSettings.KEY_WHITE_BALANCE, null);
1071            } else {
1072                // If scene mode is not auto, override the value in settings
1073                mSettings.overrideSettings(CameraSettings.KEY_FLASH_MODE,
1074                        mParameters.getFlashMode());
1075                mSettings.overrideSettings(CameraSettings.KEY_FOCUS_MODE,
1076                        mParameters.getFocusMode());
1077                mSettings.overrideSettings(CameraSettings.KEY_WHITE_BALANCE,
1078                        mParameters.getWhiteBalance());
1079            }
1080        }
1081        mSettings.setVisible(true);
1082    }
1083
1084    public void onClick(View v) {
1085        switch (v.getId()) {
1086            case R.id.btn_retake:
1087                hidePostCaptureAlert();
1088                restartPreview();
1089                break;
1090            case R.id.review_thumbnail:
1091                if (isCameraIdle()) {
1092                    viewLastImage();
1093                }
1094                break;
1095            case R.id.btn_done:
1096                doAttach();
1097                break;
1098            case R.id.btn_cancel:
1099                doCancel();
1100        }
1101    }
1102
1103    private Bitmap createCaptureBitmap(byte[] data) {
1104        // This is really stupid...we just want to read the orientation in
1105        // the jpeg header.
1106        String filepath = ImageManager.getTempJpegPath();
1107        int degree = 0;
1108        if (saveDataToFile(filepath, data)) {
1109            degree = ImageManager.getExifOrientation(filepath);
1110            new File(filepath).delete();
1111        }
1112
1113        // Limit to 50k pixels so we can return it in the intent.
1114        Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1115        bitmap = Util.rotate(bitmap, degree);
1116        return bitmap;
1117    }
1118
1119    private void doAttach() {
1120        if (mPausing) {
1121            return;
1122        }
1123
1124        byte[] data = mImageCapture.getLastCaptureData();
1125
1126        if (mCropValue == null) {
1127            // First handle the no crop case -- just return the value.  If the
1128            // caller specifies a "save uri" then write the data to it's
1129            // stream. Otherwise, pass back a scaled down version of the bitmap
1130            // directly in the extras.
1131            if (mSaveUri != null) {
1132                OutputStream outputStream = null;
1133                try {
1134                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1135                    outputStream.write(data);
1136                    outputStream.close();
1137
1138                    setResult(RESULT_OK);
1139                    finish();
1140                } catch (IOException ex) {
1141                    // ignore exception
1142                } finally {
1143                    Util.closeSilently(outputStream);
1144                }
1145            } else {
1146                Bitmap bitmap = createCaptureBitmap(data);
1147                setResult(RESULT_OK,
1148                        new Intent("inline-data").putExtra("data", bitmap));
1149                finish();
1150            }
1151        } else {
1152            // Save the image to a temp file and invoke the cropper
1153            Uri tempUri = null;
1154            FileOutputStream tempStream = null;
1155            try {
1156                File path = getFileStreamPath(sTempCropFilename);
1157                path.delete();
1158                tempStream = openFileOutput(sTempCropFilename, 0);
1159                tempStream.write(data);
1160                tempStream.close();
1161                tempUri = Uri.fromFile(path);
1162            } catch (FileNotFoundException ex) {
1163                setResult(Activity.RESULT_CANCELED);
1164                finish();
1165                return;
1166            } catch (IOException ex) {
1167                setResult(Activity.RESULT_CANCELED);
1168                finish();
1169                return;
1170            } finally {
1171                Util.closeSilently(tempStream);
1172            }
1173
1174            Bundle newExtras = new Bundle();
1175            if (mCropValue.equals("circle")) {
1176                newExtras.putString("circleCrop", "true");
1177            }
1178            if (mSaveUri != null) {
1179                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1180            } else {
1181                newExtras.putBoolean("return-data", true);
1182            }
1183
1184            Intent cropIntent = new Intent("com.android.camera.action.CROP");
1185
1186            cropIntent.setData(tempUri);
1187            cropIntent.putExtras(newExtras);
1188
1189            startActivityForResult(cropIntent, CROP_MSG);
1190        }
1191    }
1192
1193    private void doCancel() {
1194        setResult(RESULT_CANCELED, new Intent());
1195        finish();
1196    }
1197
1198    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
1199        if (mPausing) {
1200            return;
1201        }
1202        switch (button.getId()) {
1203            case R.id.shutter_button:
1204                doFocus(pressed);
1205                break;
1206        }
1207    }
1208
1209    public void onShutterButtonClick(ShutterButton button) {
1210        if (mPausing) {
1211            return;
1212        }
1213        switch (button.getId()) {
1214            case R.id.shutter_button:
1215                doSnap();
1216                break;
1217        }
1218    }
1219
1220    private OnScreenHint mStorageHint;
1221
1222    private void updateStorageHint(int remaining) {
1223        String noStorageText = null;
1224
1225        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
1226            String state = Environment.getExternalStorageState();
1227            if (state == Environment.MEDIA_CHECKING ||
1228                    ImageManager.isMediaScannerScanning(getContentResolver())) {
1229                noStorageText = getString(R.string.preparing_sd);
1230            } else {
1231                noStorageText = getString(R.string.no_storage);
1232            }
1233        } else if (remaining < 1) {
1234            noStorageText = getString(R.string.not_enough_space);
1235        }
1236
1237        if (noStorageText != null) {
1238            if (mStorageHint == null) {
1239                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1240            } else {
1241                mStorageHint.setText(noStorageText);
1242            }
1243            mStorageHint.show();
1244        } else if (mStorageHint != null) {
1245            mStorageHint.cancel();
1246            mStorageHint = null;
1247        }
1248    }
1249
1250    private void installIntentFilter() {
1251        // install an intent filter to receive SD card related events.
1252        IntentFilter intentFilter =
1253                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1254        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1255        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
1256        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1257        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1258        intentFilter.addDataScheme("file");
1259        registerReceiver(mReceiver, intentFilter);
1260        mDidRegister = true;
1261    }
1262
1263    private void initializeFocusTone() {
1264        // Initialize focus tone generator.
1265        try {
1266            mFocusToneGenerator = new ToneGenerator(
1267                    AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1268        } catch (Throwable ex) {
1269            Log.w(TAG, "Exception caught while creating tone generator: ", ex);
1270            mFocusToneGenerator = null;
1271        }
1272    }
1273
1274    private void readPreference() {
1275        mRecordLocation = RecordLocationPreference.get(
1276                mPreferences, getContentResolver());
1277        mFocusMode = mPreferences.getString(
1278                CameraSettings.KEY_FOCUS_MODE,
1279                getString(R.string.pref_camera_focusmode_default));
1280    }
1281
1282    @Override
1283    public void onResume() {
1284        super.onResume();
1285
1286        mPausing = false;
1287        mJpegPictureCallbackTime = 0;
1288        mZoomValue = 0;
1289        mImageCapture = new ImageCapture();
1290
1291        // Start the preview if it is not started.
1292        if (!mPreviewing && !mStartPreviewFail) {
1293            try {
1294                startPreview();
1295            } catch (CameraHardwareException e) {
1296                showCameraErrorAndFinish();
1297                return;
1298            }
1299        }
1300
1301        if (mSurfaceHolder != null) {
1302            // If first time initialization is not finished, put it in the
1303            // message queue.
1304            if (!mFirstTimeInitialized) {
1305                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1306            } else {
1307                initializeSecondTime();
1308            }
1309        }
1310        keepScreenOnAwhile();
1311    }
1312
1313    private static ImageManager.DataLocation dataLocation() {
1314        return ImageManager.DataLocation.EXTERNAL;
1315    }
1316
1317    @Override
1318    protected void onPause() {
1319        mPausing = true;
1320        stopPreview();
1321        // Close the camera now because other activities may need to use it.
1322        closeCamera();
1323        resetScreenOn();
1324
1325        if (mSettings != null && mSettings.isVisible()) {
1326            mSettings.setVisible(false);
1327        }
1328
1329        if (mFirstTimeInitialized) {
1330            mOrientationListener.disable();
1331            mGpsIndicator.setMode(GPS_MODE_OFF);
1332            if (!mIsImageCaptureIntent) {
1333                mThumbController.storeData(
1334                        ImageManager.getLastImageThumbPath());
1335            }
1336            hidePostCaptureAlert();
1337        }
1338
1339        if (mDidRegister) {
1340            unregisterReceiver(mReceiver);
1341            mDidRegister = false;
1342        }
1343        stopReceivingLocationUpdates();
1344
1345        if (mFocusToneGenerator != null) {
1346            mFocusToneGenerator.release();
1347            mFocusToneGenerator = null;
1348        }
1349
1350        if (mStorageHint != null) {
1351            mStorageHint.cancel();
1352            mStorageHint = null;
1353        }
1354
1355        // If we are in an image capture intent and has taken
1356        // a picture, we just clear it in onPause.
1357        mImageCapture.clearLastData();
1358        mImageCapture = null;
1359
1360        // This is necessary to make the ZoomButtonsController unregister
1361        // its configuration change receiver.
1362        if (mZoomButtons != null) {
1363            mZoomButtons.setVisible(false);
1364        }
1365
1366        // Remove the messages in the event queue.
1367        mHandler.removeMessages(RESTART_PREVIEW);
1368        mHandler.removeMessages(FIRST_TIME_INIT);
1369
1370        super.onPause();
1371    }
1372
1373    @Override
1374    protected void onActivityResult(
1375            int requestCode, int resultCode, Intent data) {
1376        switch (requestCode) {
1377            case CROP_MSG: {
1378                Intent intent = new Intent();
1379                if (data != null) {
1380                    Bundle extras = data.getExtras();
1381                    if (extras != null) {
1382                        intent.putExtras(extras);
1383                    }
1384                }
1385                setResult(resultCode, intent);
1386                finish();
1387
1388                File path = getFileStreamPath(sTempCropFilename);
1389                path.delete();
1390
1391                break;
1392            }
1393        }
1394    }
1395
1396    private boolean canTakePicture() {
1397        return isCameraIdle() && mPreviewing && (mPicturesRemaining > 0);
1398    }
1399
1400    private void autoFocus() {
1401        // Initiate autofocus only when preview is started and snapshot is not
1402        // in progress.
1403        if (canTakePicture()) {
1404            Log.v(TAG, "Start autofocus.");
1405            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1406            mFocusStartTime = System.currentTimeMillis();
1407            mFocusState = FOCUSING;
1408            updateFocusIndicator();
1409            mCameraDevice.autoFocus(mAutoFocusCallback);
1410        }
1411    }
1412
1413    private void cancelAutoFocus() {
1414        // User releases half-pressed focus key.
1415        if (mFocusState == FOCUSING || mFocusState == FOCUS_SUCCESS
1416                || mFocusState == FOCUS_FAIL) {
1417            Log.v(TAG, "Cancel autofocus.");
1418            mCameraDevice.cancelAutoFocus();
1419        }
1420        if (mFocusState != FOCUSING_SNAP_ON_FINISH) {
1421            clearFocusState();
1422        }
1423    }
1424
1425    private void clearFocusState() {
1426        mFocusState = FOCUS_NOT_STARTED;
1427        updateFocusIndicator();
1428    }
1429
1430    private void updateFocusIndicator() {
1431        if (mFocusRectangle == null) return;
1432
1433        if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) {
1434            mFocusRectangle.showStart();
1435        } else if (mFocusState == FOCUS_SUCCESS) {
1436            mFocusRectangle.showSuccess();
1437        } else if (mFocusState == FOCUS_FAIL) {
1438            mFocusRectangle.showFail();
1439        } else {
1440            mFocusRectangle.clear();
1441        }
1442    }
1443
1444    @Override
1445    public void onBackPressed() {
1446        if (!isCameraIdle()) {
1447            // ignore backs while we're taking a picture
1448            return;
1449        }
1450        super.onBackPressed();
1451    }
1452
1453    @Override
1454    public boolean onKeyDown(int keyCode, KeyEvent event) {
1455        switch (keyCode) {
1456            case KeyEvent.KEYCODE_FOCUS:
1457                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1458                    doFocus(true);
1459                }
1460                return true;
1461            case KeyEvent.KEYCODE_CAMERA:
1462                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1463                    doSnap();
1464                }
1465                return true;
1466            case KeyEvent.KEYCODE_DPAD_CENTER:
1467                // If we get a dpad center event without any focused view, move
1468                // the focus to the shutter button and press it.
1469                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1470                    // Start auto-focus immediately to reduce shutter lag. After
1471                    // the shutter button gets the focus, doFocus() will be
1472                    // called again but it is fine.
1473                    doFocus(true);
1474                    if (mShutterButton.isInTouchMode()) {
1475                        mShutterButton.requestFocusFromTouch();
1476                    } else {
1477                        mShutterButton.requestFocus();
1478                    }
1479                    mShutterButton.setPressed(true);
1480                }
1481                return true;
1482        }
1483
1484        return super.onKeyDown(keyCode, event);
1485    }
1486
1487    @Override
1488    public boolean onKeyUp(int keyCode, KeyEvent event) {
1489        switch (keyCode) {
1490            case KeyEvent.KEYCODE_FOCUS:
1491                if (mFirstTimeInitialized) {
1492                    doFocus(false);
1493                }
1494                return true;
1495            case KeyEvent.KEYCODE_MENU:
1496                if (mIsImageCaptureIntent) {
1497                    showOnScreenSettings();
1498                    return true;
1499                }
1500                break;
1501        }
1502        return super.onKeyUp(keyCode, event);
1503    }
1504
1505    private void doSnap() {
1506        Log.v(TAG, "doSnap: mFocusState=" + mFocusState);
1507        // If the user has half-pressed the shutter and focus is completed, we
1508        // can take the photo right away. If the focus mode is infinity, we can
1509        // also take the photo.
1510        if (mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)
1511                || (mFocusState == FOCUS_SUCCESS
1512                || mFocusState == FOCUS_FAIL)) {
1513            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1514            mImageCapture.onSnap();
1515        } else if (mFocusState == FOCUSING) {
1516            // Half pressing the shutter (i.e. the focus button event) will
1517            // already have requested AF for us, so just request capture on
1518            // focus here.
1519            mFocusState = FOCUSING_SNAP_ON_FINISH;
1520        } else if (mFocusState == FOCUS_NOT_STARTED) {
1521            // Focus key down event is dropped for some reasons. Just ignore.
1522        }
1523    }
1524
1525    private void doFocus(boolean pressed) {
1526        // Do the focus if the mode is not infinity.
1527        if (!mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)) {
1528            if (pressed) {  // Focus key down.
1529                autoFocus();
1530            } else {  // Focus key up.
1531                cancelAutoFocus();
1532            }
1533        }
1534    }
1535
1536    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1537        // Make sure we have a surface in the holder before proceeding.
1538        if (holder.getSurface() == null) {
1539            Log.d(TAG, "holder.getSurface() == null");
1540            return;
1541        }
1542
1543        // We need to save the holder for later use, even when the mCameraDevice
1544        // is null. This could happen if onResume() is invoked after this
1545        // function.
1546        mSurfaceHolder = holder;
1547
1548        // The mCameraDevice will be null if it fails to connect to the camera
1549        // hardware. In this case we will show a dialog and then finish the
1550        // activity, so it's OK to ignore it.
1551        if (mCameraDevice == null) return;
1552
1553        // Sometimes surfaceChanged is called after onPause or before onResume.
1554        // Ignore it.
1555        if (mPausing || isFinishing()) return;
1556
1557        if (mPreviewing && holder.isCreating()) {
1558            // Set preview display if the surface is being created and preview
1559            // was already started. That means preview display was set to null
1560            // and we need to set it now.
1561            setPreviewDisplay(holder);
1562        } else {
1563            // 1. Restart the preview if the size of surface was changed. The
1564            // framework may not support changing preview display on the fly.
1565            // 2. Start the preview now if surface was destroyed and preview
1566            // stopped.
1567            restartPreview();
1568        }
1569
1570        // If first time initialization is not finished, send a message to do
1571        // it later. We want to finish surfaceChanged as soon as possible to let
1572        // user see preview first.
1573        if (!mFirstTimeInitialized) {
1574            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1575        } else {
1576            initializeSecondTime();
1577        }
1578    }
1579
1580    public void surfaceCreated(SurfaceHolder holder) {
1581    }
1582
1583    public void surfaceDestroyed(SurfaceHolder holder) {
1584        stopPreview();
1585        mSurfaceHolder = null;
1586    }
1587
1588    private void closeCamera() {
1589        if (mCameraDevice != null) {
1590            CameraHolder.instance().release();
1591            if (mZoomButtons != null) mCameraDevice.setZoomCallback(null);
1592            mCameraDevice = null;
1593            mPreviewing = false;
1594        }
1595    }
1596
1597    private void ensureCameraDevice() throws CameraHardwareException {
1598        if (mCameraDevice == null) {
1599            mCameraDevice = CameraHolder.instance().open();
1600            mInitialParams = mCameraDevice.getParameters();
1601        }
1602    }
1603
1604    private void updateLastImage() {
1605        IImageList list = ImageManager.makeImageList(
1606            mContentResolver,
1607            dataLocation(),
1608            ImageManager.INCLUDE_IMAGES,
1609            ImageManager.SORT_ASCENDING,
1610            ImageManager.CAMERA_IMAGE_BUCKET_ID);
1611        int count = list.getCount();
1612        if (count > 0) {
1613            IImage image = list.getImageAt(count - 1);
1614            Uri uri = image.fullSizeImageUri();
1615            mThumbController.setData(uri, image.miniThumbBitmap());
1616        } else {
1617            mThumbController.setData(null, null);
1618        }
1619        list.close();
1620    }
1621
1622    private void showCameraErrorAndFinish() {
1623        Resources ress = getResources();
1624        Util.showFatalErrorAndFinish(Camera.this,
1625                ress.getString(R.string.camera_error_title),
1626                ress.getString(R.string.cannot_connect_camera));
1627    }
1628
1629    private void restartPreview() {
1630        try {
1631            startPreview();
1632        } catch (CameraHardwareException e) {
1633            showCameraErrorAndFinish();
1634            return;
1635        }
1636    }
1637
1638    private void setPreviewDisplay(SurfaceHolder holder) {
1639        try {
1640            mCameraDevice.setPreviewDisplay(holder);
1641        } catch (Throwable ex) {
1642            closeCamera();
1643            throw new RuntimeException("setPreviewDisplay failed", ex);
1644        }
1645    }
1646
1647    private void startPreview() throws CameraHardwareException {
1648        if (mPausing || isFinishing()) return;
1649
1650        ensureCameraDevice();
1651
1652        // If we're previewing already, stop the preview first (this will blank
1653        // the screen).
1654        if (mPreviewing) stopPreview();
1655
1656        setPreviewDisplay(mSurfaceHolder);
1657        setCameraParameters();
1658
1659        final long wallTimeStart = SystemClock.elapsedRealtime();
1660        final long threadTimeStart = Debug.threadCpuTimeNanos();
1661
1662        // Set one shot preview callback for latency measurement.
1663        mCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
1664        mCameraDevice.setErrorCallback(mErrorCallback);
1665
1666        try {
1667            Log.v(TAG, "startPreview");
1668            mCameraDevice.startPreview();
1669        } catch (Throwable ex) {
1670            closeCamera();
1671            throw new RuntimeException("startPreview failed", ex);
1672        }
1673        mPreviewing = true;
1674        mZooming = false;
1675        mStatus = IDLE;
1676
1677        long threadTimeEnd = Debug.threadCpuTimeNanos();
1678        long wallTimeEnd = SystemClock.elapsedRealtime();
1679        if ((wallTimeEnd - wallTimeStart) > 3000) {
1680            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart)
1681                    + " ms. Thread time was"
1682                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
1683        }
1684    }
1685
1686    private void stopPreview() {
1687        if (mCameraDevice != null && mPreviewing) {
1688            Log.v(TAG, "stopPreview");
1689            mCameraDevice.stopPreview();
1690        }
1691        mPreviewing = false;
1692        // If auto focus was in progress, it would have been canceled.
1693        clearFocusState();
1694    }
1695
1696    private Size getOptimalPreviewSize(List<Size> sizes, double targetRatio) {
1697        final double ASPECT_TOLERANCE = 0.05;
1698        if (sizes == null) return null;
1699
1700        Size optimalSize = null;
1701        double minDiff = Double.MAX_VALUE;
1702
1703        // Because of bugs of overlay and layout, we sometimes will try to
1704        // layout the viewfinder in the portrait orientation and thus get the
1705        // wrong size of mSurfaceView. When we change the preview size, the
1706        // new overlay will be created before the old one closed, which causes
1707        // an exception. For now, just get the screen size
1708
1709        Display display = getWindowManager().getDefaultDisplay();
1710        int targetHeight = Math.min(display.getHeight(), display.getWidth());
1711
1712        if (targetHeight <= 0) {
1713            // We don't know the size of SurefaceView, use screen height
1714            WindowManager windowManager = (WindowManager)
1715                    getSystemService(Context.WINDOW_SERVICE);
1716            targetHeight = windowManager.getDefaultDisplay().getHeight();
1717        }
1718
1719        // Try to find an size match aspect ratio and size
1720        for (Size size : sizes) {
1721            double ratio = (double) size.width / size.height;
1722            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
1723            if (Math.abs(size.height - targetHeight) < minDiff) {
1724                optimalSize = size;
1725                minDiff = Math.abs(size.height - targetHeight);
1726            }
1727        }
1728
1729        // Cannot find the one match the aspect ratio, ignore the requirement
1730        if (optimalSize == null) {
1731            Log.v(TAG, "No preview size match the aspect ratio");
1732            minDiff = Double.MAX_VALUE;
1733            for (Size size : sizes) {
1734                if (Math.abs(size.height - targetHeight) < minDiff) {
1735                    optimalSize = size;
1736                    minDiff = Math.abs(size.height - targetHeight);
1737                }
1738            }
1739        }
1740        Log.v(TAG, String.format(
1741                "Optimal preview size is %sx%s",
1742                optimalSize.width, optimalSize.height));
1743        return optimalSize;
1744    }
1745
1746    private static boolean isSupported(String value, List<String> supported) {
1747        return supported == null ? false : supported.indexOf(value) >= 0;
1748    }
1749
1750    private void setCameraParameters() {
1751        mParameters = mCameraDevice.getParameters();
1752
1753        // Since change scene mode may change supported values,
1754        // Set scene mode first,
1755        String sceneMode = mPreferences.getString(
1756                CameraSettings.KEY_SCENE_MODE,
1757                getString(R.string.pref_camera_scenemode_default));
1758        if (isSupported(sceneMode, mParameters.getSupportedSceneModes())) {
1759            if (!mParameters.getSceneMode().equals(sceneMode)) {
1760                mParameters.setSceneMode(sceneMode);
1761                mCameraDevice.setParameters(mParameters);
1762
1763                // Setting scene mode will change the settings of flash mode,
1764                // white balance, and focus mode. Here we read back the
1765                // parameters, so we can know those settings.
1766                mParameters = mCameraDevice.getParameters();
1767            }
1768        } else {
1769            sceneMode = mParameters.getSceneMode();
1770            if (sceneMode == null) {
1771                sceneMode = Parameters.SCENE_MODE_AUTO;
1772            }
1773        }
1774
1775        // Reset preview frame rate to the maximum because it may be lowered by
1776        // video camera application.
1777        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1778        if (frameRates != null) {
1779            Integer max = Collections.max(frameRates);
1780            mParameters.setPreviewFrameRate(max);
1781        }
1782
1783        // Set picture size.
1784        String pictureSize = mPreferences.getString(
1785                CameraSettings.KEY_PICTURE_SIZE, null);
1786        if (pictureSize == null) {
1787            CameraSettings.initialCameraPictureSize(this, mParameters);
1788        } else {
1789            List<Size> supported = mParameters.getSupportedPictureSizes();
1790            CameraSettings.setCameraPictureSize(
1791                    pictureSize, supported, mParameters);
1792        }
1793
1794        // Set the preview frame aspect ratio according to the picture size.
1795        Size size = mParameters.getPictureSize();
1796        PreviewFrameLayout frameLayout =
1797                (PreviewFrameLayout) findViewById(R.id.frame_layout);
1798        frameLayout.setAspectRatio((double) size.width / size.height);
1799
1800        // Set a preview size that is closest to the viewfinder height and has
1801        // the right aspect ratio.
1802        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1803        Size optimalSize = getOptimalPreviewSize(
1804                sizes, (double) size.width / size.height);
1805        if (optimalSize != null) {
1806            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1807        }
1808
1809        // Set JPEG quality.
1810        String jpegQuality = mPreferences.getString(
1811                CameraSettings.KEY_JPEG_QUALITY,
1812                getString(R.string.pref_camera_jpegquality_default));
1813        mParameters.setJpegQuality(getQualityNumber(jpegQuality));
1814
1815        // Set zoom.
1816        if (mParameters.isZoomSupported()) {
1817            mParameters.setZoom(mZoomValue);
1818        }
1819
1820        // For the following settings, we need to check if the settings are
1821        // still supported by latest driver, if not, ignore the settings.
1822
1823        // Set color effect parameter.
1824        String colorEffect = mPreferences.getString(
1825                CameraSettings.KEY_COLOR_EFFECT,
1826                getString(R.string.pref_camera_coloreffect_default));
1827        if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
1828            mParameters.setColorEffect(colorEffect);
1829        }
1830
1831        // If scene mode is set, we cannot set flash mode, white balance, and
1832        // focus mode, instead, we read it from driver
1833        String flashMode;
1834        String whiteBalance;
1835        if (!Parameters.SCENE_MODE_AUTO.equals(sceneMode)) {
1836            flashMode = mParameters.getFlashMode();
1837            whiteBalance = mParameters.getWhiteBalance();
1838            mFocusMode = mParameters.getFocusMode();
1839            if (mSettings != null) {
1840                mSettings.overrideSettings(
1841                        CameraSettings.KEY_FLASH_MODE, flashMode);
1842                mSettings.overrideSettings(
1843                        CameraSettings.KEY_WHITE_BALANCE, whiteBalance);
1844                mSettings.overrideSettings(
1845                        CameraSettings.KEY_FOCUS_MODE, mFocusMode);
1846            }
1847        } else {
1848            if (mSettings != null) {
1849                mSettings.overrideSettings(CameraSettings.KEY_FLASH_MODE, null);
1850                mSettings.overrideSettings(CameraSettings.KEY_FOCUS_MODE, null);
1851                mSettings.overrideSettings(
1852                        CameraSettings.KEY_WHITE_BALANCE, null);
1853            }
1854
1855            // Set flash mode.
1856            flashMode = mPreferences.getString(
1857                    CameraSettings.KEY_FLASH_MODE,
1858                    getString(R.string.pref_camera_flashmode_default));
1859            List<String> supportedFlash = mParameters.getSupportedFlashModes();
1860            if (isSupported(flashMode, supportedFlash)) {
1861                mParameters.setFlashMode(flashMode);
1862            } else {
1863                flashMode = mParameters.getFlashMode();
1864                if (flashMode == null) {
1865                    flashMode = getString(
1866                            R.string.pref_camera_flashmode_no_flash);
1867                }
1868            }
1869
1870            // Set white balance parameter.
1871            whiteBalance = mPreferences.getString(
1872                    CameraSettings.KEY_WHITE_BALANCE,
1873                    getString(R.string.pref_camera_whitebalance_default));
1874            if (isSupported(whiteBalance,
1875                    mParameters.getSupportedWhiteBalance())) {
1876                mParameters.setWhiteBalance(whiteBalance);
1877            } else {
1878                whiteBalance = mParameters.getWhiteBalance();
1879                if (whiteBalance == null) {
1880                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1881                }
1882            }
1883
1884            // Set focus mode.
1885            mFocusMode = mPreferences.getString(
1886                    CameraSettings.KEY_FOCUS_MODE,
1887                    getString(R.string.pref_camera_focusmode_default));
1888            if (isSupported(mFocusMode, mParameters.getSupportedFocusModes())) {
1889                mParameters.setFocusMode(mFocusMode);
1890            } else {
1891                mFocusMode = mParameters.getFocusMode();
1892                if (mFocusMode == null) {
1893                    mFocusMode = Parameters.FOCUS_MODE_AUTO;
1894                }
1895            }
1896
1897            mCameraDevice.setParameters(mParameters);
1898        }
1899
1900        // We post the runner because this function can be called from
1901        // non-UI thread (i.e., startPreviewThread).
1902        final String finalWhiteBalance = whiteBalance;
1903        final String finalFlashMode = flashMode;
1904        final String finalSceneMode =
1905                Parameters.SCENE_MODE_AUTO.equals(sceneMode)
1906                ? SCENE_MODE_OFF
1907                : SCENE_MODE_ON;
1908
1909        mHandler.post(new Runnable() {
1910            public void run() {
1911                mFocusIndicator.setMode(mFocusMode);
1912                mWhitebalanceIndicator.setMode(finalWhiteBalance);
1913                mSceneModeIndicator.setMode(finalSceneMode);
1914                mFlashIndicator.setMode(finalFlashMode);
1915            }
1916        });
1917    }
1918
1919    private void gotoGallery() {
1920        MenuHelper.gotoCameraImageGallery(this);
1921    }
1922
1923    private void viewLastImage() {
1924        if (mThumbController.isUriValid()) {
1925            Uri targetUri = mThumbController.getUri();
1926            targetUri = targetUri.buildUpon().appendQueryParameter(
1927                    "bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
1928            Intent intent = new Intent(this, ReviewImage.class);
1929            intent.setData(targetUri);
1930            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
1931            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
1932            intent.putExtra("com.android.camera.ReviewMode", true);
1933            try {
1934                startActivity(intent);
1935            } catch (ActivityNotFoundException ex) {
1936                Log.e(TAG, "review image fail", ex);
1937            }
1938        } else {
1939            Log.e(TAG, "Can't view last image.");
1940        }
1941    }
1942
1943    private void startReceivingLocationUpdates() {
1944        if (mLocationManager != null) {
1945            try {
1946                mLocationManager.requestLocationUpdates(
1947                        LocationManager.NETWORK_PROVIDER,
1948                        1000,
1949                        0F,
1950                        mLocationListeners[1]);
1951            } catch (java.lang.SecurityException ex) {
1952                Log.i(TAG, "fail to request location update, ignore", ex);
1953            } catch (IllegalArgumentException ex) {
1954                Log.d(TAG, "provider does not exist " + ex.getMessage());
1955            }
1956            try {
1957                mLocationManager.requestLocationUpdates(
1958                        LocationManager.GPS_PROVIDER,
1959                        1000,
1960                        0F,
1961                        mLocationListeners[0]);
1962            } catch (java.lang.SecurityException ex) {
1963                Log.i(TAG, "fail to request location update, ignore", ex);
1964            } catch (IllegalArgumentException ex) {
1965                Log.d(TAG, "provider does not exist " + ex.getMessage());
1966            }
1967        }
1968    }
1969
1970    private void stopReceivingLocationUpdates() {
1971        if (mLocationManager != null) {
1972            for (int i = 0; i < mLocationListeners.length; i++) {
1973                try {
1974                    mLocationManager.removeUpdates(mLocationListeners[i]);
1975                } catch (Exception ex) {
1976                    Log.i(TAG, "fail to remove location listners, ignore", ex);
1977                }
1978            }
1979        }
1980    }
1981
1982    private Location getCurrentLocation() {
1983        // go in best to worst order
1984        for (int i = 0; i < mLocationListeners.length; i++) {
1985            Location l = mLocationListeners[i].current();
1986            if (l != null) return l;
1987        }
1988        return null;
1989    }
1990
1991    private boolean isCameraIdle() {
1992        return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED;
1993    }
1994
1995    private boolean isImageCaptureIntent() {
1996        String action = getIntent().getAction();
1997        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1998    }
1999
2000    private void setupCaptureParams() {
2001        Bundle myExtras = getIntent().getExtras();
2002        if (myExtras != null) {
2003            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2004            mCropValue = myExtras.getString("crop");
2005        }
2006    }
2007
2008    private void showPostCaptureAlert() {
2009        if (mIsImageCaptureIntent) {
2010            findViewById(R.id.shutter_button).setVisibility(View.INVISIBLE);
2011            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2012            for (int id : pickIds) {
2013                View button = findViewById(id);
2014                ((View) button.getParent()).setVisibility(View.VISIBLE);
2015            }
2016        }
2017    }
2018
2019    private void hidePostCaptureAlert() {
2020        if (mIsImageCaptureIntent) {
2021            findViewById(R.id.shutter_button).setVisibility(View.VISIBLE);
2022            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2023            for (int id : pickIds) {
2024                View button = findViewById(id);
2025                ((View) button.getParent()).setVisibility(View.GONE);
2026            }
2027        }
2028    }
2029
2030    private int calculatePicturesRemaining() {
2031        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
2032        return mPicturesRemaining;
2033    }
2034
2035    @Override
2036    public boolean onPrepareOptionsMenu(Menu menu) {
2037        super.onPrepareOptionsMenu(menu);
2038        // Only show the menu when camera is idle.
2039        for (int i = 0; i < menu.size(); i++) {
2040            menu.getItem(i).setVisible(isCameraIdle());
2041        }
2042
2043        return true;
2044    }
2045
2046    @Override
2047    public boolean onCreateOptionsMenu(Menu menu) {
2048        super.onCreateOptionsMenu(menu);
2049
2050        if (mIsImageCaptureIntent) {
2051            // No options menu for attach mode.
2052            return false;
2053        } else {
2054            addBaseMenuItems(menu);
2055        }
2056        return true;
2057    }
2058
2059    private void addBaseMenuItems(Menu menu) {
2060        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
2061                MenuHelper.POSITION_GOTO_GALLERY,
2062                R.string.camera_gallery_photos_text)
2063                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2064            public boolean onMenuItemClick(MenuItem item) {
2065                gotoGallery();
2066                return true;
2067            }
2068        });
2069        gallery.setIcon(android.R.drawable.ic_menu_gallery);
2070        mGalleryItems.add(gallery);
2071
2072        MenuItem item = menu.add(Menu.NONE, Menu.NONE,
2073                MenuHelper.POSITION_CAMERA_SETTING, R.string.settings)
2074                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2075            public boolean onMenuItemClick(MenuItem item) {
2076                showOnScreenSettings();
2077                return true;
2078            }
2079        });
2080        item.setIcon(android.R.drawable.ic_menu_preferences);
2081    }
2082
2083    public boolean onSwitchChanged(Switcher source, boolean onOff) {
2084        if (onOff == SWITCH_VIDEO) {
2085            if (!isCameraIdle()) return false;
2086            MenuHelper.gotoVideoMode(this);
2087            finish();
2088        }
2089        return true;
2090    }
2091
2092    public void onSharedPreferenceChanged(
2093            SharedPreferences preferences, String key) {
2094        // ignore the events after "onPause()"
2095        if (mPausing) return;
2096
2097        if (CameraSettings.KEY_RECORD_LOCATION.equals(key)) {
2098            mRecordLocation = RecordLocationPreference.get(
2099                    preferences, getContentResolver());
2100            if (mRecordLocation) {
2101                startReceivingLocationUpdates();
2102            } else {
2103                stopReceivingLocationUpdates();
2104            }
2105        } else {
2106            // All preferences except RECORD_LOCATION are camera parameters.
2107            // Call setCameraParameters to take effect now.
2108            setCameraParameters();
2109        }
2110    }
2111
2112    @Override
2113    public void onUserInteraction() {
2114        super.onUserInteraction();
2115        keepScreenOnAwhile();
2116    }
2117
2118    private void resetScreenOn() {
2119        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2120        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2121    }
2122
2123    private void keepScreenOnAwhile() {
2124        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2125        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2126        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2127    }
2128
2129    private static String[] mQualityStrings = {"superfine", "fine", "normal"};
2130    private static String[] mQualityNumbers = SystemProperties.get(
2131            "ro.media.enc.jpeg.quality", "85,75,65").split(",");
2132    private static int DEFAULT_QUALITY = 85;
2133
2134    // Translate from a quality string to a quality number using the system
2135    // properties.
2136    private static int getQualityNumber(String jpegQuality) {
2137        // Find the index of the input string
2138        int index = Util.indexOf(mQualityStrings, jpegQuality);
2139
2140        if (index == -1 || index > mQualityNumbers.length - 1) {
2141            return DEFAULT_QUALITY;
2142        }
2143
2144        try {
2145            return Integer.parseInt(mQualityNumbers[index]);
2146        } catch (NumberFormatException ex) {
2147            return DEFAULT_QUALITY;
2148        }
2149    }
2150}
2151
2152class FocusRectangle extends View {
2153
2154    @SuppressWarnings("unused")
2155    private static final String TAG = "FocusRectangle";
2156
2157    public FocusRectangle(Context context, AttributeSet attrs) {
2158        super(context, attrs);
2159    }
2160
2161    private void setDrawable(int resid) {
2162        setBackgroundDrawable(getResources().getDrawable(resid));
2163    }
2164
2165    public void showStart() {
2166        setDrawable(R.drawable.focus_focusing);
2167    }
2168
2169    public void showSuccess() {
2170        setDrawable(R.drawable.focus_focused);
2171    }
2172
2173    public void showFail() {
2174        setDrawable(R.drawable.focus_focus_failed);
2175    }
2176
2177    public void clear() {
2178        setBackgroundDrawable(null);
2179    }
2180}
2181