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