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