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