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