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