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