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