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