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