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