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