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