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