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