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