Camera.java revision cc8d7565f1500b6db20dd7d4c8cc3b6434067e0b
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 android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.graphics.Matrix;
32import android.hardware.Camera.Parameters;
33import android.hardware.Camera.PictureCallback;
34import android.hardware.Camera.Size;
35import android.location.Location;
36import android.location.LocationManager;
37import android.location.LocationProvider;
38import android.media.AudioManager;
39import android.media.ToneGenerator;
40import android.net.Uri;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Debug;
44import android.os.Environment;
45import android.os.Handler;
46import android.os.Message;
47import android.os.SystemClock;
48import android.preference.PreferenceManager;
49import android.preference.PreferenceScreen;
50import android.provider.MediaStore;
51import android.text.format.DateFormat;
52import android.util.AttributeSet;
53import android.util.Log;
54import android.view.GestureDetector;
55import android.view.KeyEvent;
56import android.view.LayoutInflater;
57import android.view.Menu;
58import android.view.MenuItem;
59import android.view.MotionEvent;
60import android.view.OrientationEventListener;
61import android.view.SurfaceHolder;
62import android.view.View;
63import android.view.ViewGroup;
64import android.view.Window;
65import android.view.WindowManager;
66import android.view.MenuItem.OnMenuItemClickListener;
67import android.widget.ImageView;
68import android.widget.ZoomButtonsController;
69
70import com.android.camera.gallery.IImage;
71import com.android.camera.gallery.IImageList;
72
73import java.io.File;
74import java.io.FileNotFoundException;
75import java.io.FileOutputStream;
76import java.io.IOException;
77import java.io.OutputStream;
78import java.util.ArrayList;
79import java.util.Collections;
80import java.util.List;
81
82/**
83 * Activity of the Camera which used to see preview and take pictures.
84 */
85public class Camera extends Activity implements View.OnClickListener,
86        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
87        Switcher.OnSwitchListener, FlashButton.ModeChangeListener,
88        OnScreenSettings.OnVisibilityChangedListener,
89        OnSharedPreferenceChangeListener {
90
91    private static final String TAG = "camera";
92
93    private static final int CROP_MSG = 1;
94    private static final int FIRST_TIME_INIT = 2;
95    private static final int RESTART_PREVIEW = 3;
96    private static final int CLEAR_SCREEN_DELAY = 4;
97
98    private static final int SCREEN_DELAY = 2 * 60 * 1000;
99    private static final int FOCUS_BEEP_VOLUME = 100;
100
101    private double mZoomValue;  // The current zoom value.
102    private boolean mZooming = false;
103    private double mZoomStep;
104    private double mZoomMax;
105    public static final double ZOOM_STEP_MIN = 0.25;
106    public static final String ZOOM_STOP = "stop";
107    public static final String ZOOM_IMMEDIATE = "zoom-immediate";
108    public static final String ZOOM_CONTINUOUS = "zoom-continuous";
109    public static final double ZOOM_MIN = 1.0;
110    public static final String ZOOM_SPEED = "99";
111
112    private Parameters mParameters;
113
114    // The parameter strings to communicate with camera driver.
115    public static final String PARM_ZOOM_STATE = "zoom-state";
116    public static final String PARM_ZOOM_STEP = "zoom-step";
117    public static final String PARM_ZOOM_TO_LEVEL = "zoom-to-level";
118    public static final String PARM_ZOOM_SPEED = "zoom-speed";
119    public static final String PARM_ZOOM_MAX = "max-picture-continuous-zoom";
120
121    private OrientationEventListener mOrientationListener;
122    private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
123    private SharedPreferences mPreferences;
124
125    private static final int IDLE = 1;
126    private static final int SNAPSHOT_IN_PROGRESS = 2;
127
128    private static final boolean SWITCH_CAMERA = true;
129    private static final boolean SWITCH_VIDEO = false;
130
131    private int mStatus = IDLE;
132    private static final String sTempCropFilename = "crop-temp";
133
134    private android.hardware.Camera mCameraDevice;
135    private VideoPreview mSurfaceView;
136    private SurfaceHolder mSurfaceHolder = null;
137    private ShutterButton mShutterButton;
138    private FocusRectangle mFocusRectangle;
139    private FlashButton mFlashButton;
140    private ImageView mGpsIndicator;
141    private ToneGenerator mFocusToneGenerator;
142    private ZoomButtonsController mZoomButtons;
143    private GestureDetector mGestureDetector;
144    private Switcher mSwitcher;
145    private boolean mStartPreviewFail = false;
146
147    // mPostCaptureAlert, mLastPictureButton, mThumbController
148    // are non-null only if isImageCaptureIntent() is true.
149    private ImageView mLastPictureButton;
150    private ThumbnailController mThumbController;
151
152    private int mViewFinderWidth, mViewFinderHeight;
153
154    private ImageCapture mImageCapture = null;
155
156    private boolean mPreviewing;
157    private boolean mPausing;
158    private boolean mFirstTimeInitialized;
159    private boolean mIsImageCaptureIntent;
160    private boolean mRecordLocation;
161
162    private static final int FOCUS_NOT_STARTED = 0;
163    private static final int FOCUSING = 1;
164    private static final int FOCUSING_SNAP_ON_FINISH = 2;
165    private static final int FOCUS_SUCCESS = 3;
166    private static final int FOCUS_FAIL = 4;
167    private int mFocusState = FOCUS_NOT_STARTED;
168
169    private ContentResolver mContentResolver;
170    private boolean mDidRegister = false;
171
172    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
173
174    private LocationManager mLocationManager = null;
175
176    // Use OneShotPreviewCallback to measure the time between
177    // JpegPictureCallback and preview.
178    private final OneShotPreviewCallback mOneShotPreviewCallback =
179            new OneShotPreviewCallback();
180    private final ShutterCallback mShutterCallback = new ShutterCallback();
181    private final RawPictureCallback mRawPictureCallback =
182            new RawPictureCallback();
183    private final AutoFocusCallback mAutoFocusCallback =
184            new AutoFocusCallback();
185    private final ZoomCallback mZoomCallback = new ZoomCallback();
186    // Use the ErrorCallback to capture the crash count
187    // on the mediaserver
188    private final ErrorCallback mErrorCallback = new ErrorCallback();
189
190    private long mFocusStartTime;
191    private long mFocusCallbackTime;
192    private long mCaptureStartTime;
193    private long mShutterCallbackTime;
194    private long mRawPictureCallbackTime;
195    private long mJpegPictureCallbackTime;
196    private int mPicturesRemaining;
197
198    // These latency time are for the CameraLatency test.
199    public long mAutoFocusTime;
200    public long mShutterLag;
201    public long mShutterAndRawPictureCallbackTime;
202    public long mJpegPictureCallbackTimeLag;
203    public long mRawPictureAndJpegPictureCallbackTime;
204
205    // Add the media server tag
206    public static boolean mMediaServerDied = false;
207    // Focus mode. Options are pref_camera_focusmode_entryvalues.
208    private String mFocusMode;
209
210    private final Handler mHandler = new MainHandler();
211    private OnScreenSettings mSettings;
212
213    /**
214     * This Handler is used to post message back onto the main thread of the
215     * application
216     */
217    private class MainHandler extends Handler {
218        @Override
219        public void handleMessage(Message msg) {
220            switch (msg.what) {
221                case RESTART_PREVIEW: {
222                    restartPreview();
223                    break;
224                }
225
226                case CLEAR_SCREEN_DELAY: {
227                    getWindow().clearFlags(
228                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
229                    break;
230                }
231
232                case FIRST_TIME_INIT: {
233                    initializeFirstTime();
234                    break;
235                }
236            }
237        }
238    }
239
240    // Snapshots can only be taken after this is called. It should be called
241    // once only. We could have done these things in onCreate() but we want to
242    // make preview screen appear as soon as possible.
243    private void initializeFirstTime() {
244        if (mFirstTimeInitialized) return;
245
246        // Create orientation listenter. This should be done first because it
247        // takes some time to get first orientation.
248        mOrientationListener =
249                new OrientationEventListener(Camera.this) {
250            @Override
251            public void onOrientationChanged(int orientation) {
252                // We keep the last known orientation. So if the user
253                // first orient the camera then point the camera to
254                // floor/sky, we still have the correct orientation.
255                if (orientation != ORIENTATION_UNKNOWN) {
256                    mLastOrientation = orientation;
257                }
258            }
259        };
260        mOrientationListener.enable();
261
262        // Initialize location sevice.
263        mLocationManager = (LocationManager)
264                getSystemService(Context.LOCATION_SERVICE);
265        readPreference();
266        if (mRecordLocation) startReceivingLocationUpdates();
267
268        checkStorage();
269
270        // Initialize last picture button.
271        mContentResolver = getContentResolver();
272        if (!mIsImageCaptureIntent)  {
273            findViewById(R.id.camera_switch).setOnClickListener(this);
274            mLastPictureButton =
275                    (ImageView) findViewById(R.id.review_thumbnail);
276            mLastPictureButton.setOnClickListener(this);
277            mThumbController = new ThumbnailController(
278                    getResources(), mLastPictureButton, mContentResolver);
279            mThumbController.loadData(ImageManager.getLastImageThumbPath());
280            // Update last image thumbnail.
281            updateThumbnailButton();
282        }
283
284        // Initialize shutter button.
285        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
286        mShutterButton.setOnShutterButtonListener(this);
287        mShutterButton.setVisibility(View.VISIBLE);
288
289        mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
290        updateFocusIndicator();
291
292        // Initialize flash button
293        if (mParameters.getSupportedFlashModes() != null) {
294            mFlashButton = (FlashButton) findViewById(R.id.flash_button);
295            String flashMode = mPreferences.getString(
296                    CameraSettings.KEY_FLASH_MODE, "auto");
297            mFlashButton.setMode(flashMode);
298            mFlashButton.setVisibility(View.VISIBLE);
299            mFlashButton.setListener(this);
300        }
301
302        // Initialize GPS indicator.
303        mGpsIndicator = (ImageView) findViewById(R.id.gps_indicator);
304        mGpsIndicator.setImageResource(R.drawable.ic_camera_sym_gps);
305
306        ImageManager.ensureOSXCompatibleFolder();
307
308        installIntentFilter();
309
310        initializeFocusTone();
311
312        initializeZoom();
313
314        mFirstTimeInitialized = true;
315    }
316
317    private void updateThumbnailButton() {
318        // Update last image if URI is invalid and the storage is ready.
319        if (!mThumbController.isUriValid() && mPicturesRemaining >= 0) {
320            updateLastImage();
321        }
322        mThumbController.updateDisplayIfNeeded();
323    }
324
325    // If the activity is paused and resumed, this method will be called in
326    // onResume.
327    private void initializeSecondTime() {
328        // Start orientation listener as soon as possible because it takes
329        // some time to get first orientation.
330        mOrientationListener.enable();
331
332        // Start location update if needed.
333        readPreference();
334        if (mRecordLocation) startReceivingLocationUpdates();
335
336        installIntentFilter();
337
338        initializeFocusTone();
339
340        checkStorage();
341
342        if (!mIsImageCaptureIntent) {
343            updateThumbnailButton();
344        }
345    }
346
347    private void initializeZoom() {
348        // Check if the phone has zoom capability.
349        String zoomState = mParameters.get(PARM_ZOOM_STATE);
350        if (zoomState == null) return;
351
352        mZoomValue = Double.parseDouble(mParameters.get(PARM_ZOOM_TO_LEVEL));
353        mZoomMax = Double.parseDouble(mParameters.get(PARM_ZOOM_MAX));
354        mZoomStep = Double.parseDouble(mParameters.get(PARM_ZOOM_STEP));
355        mParameters.set(PARM_ZOOM_SPEED, ZOOM_SPEED);
356        mCameraDevice.setParameters(mParameters);
357
358        mGestureDetector = new GestureDetector(this, new ZoomGestureListener());
359        mCameraDevice.setZoomCallback(mZoomCallback);
360        mZoomButtons = new ZoomButtonsController(mSurfaceView);
361        mZoomButtons.setAutoDismissed(true);
362        mZoomButtons.setZoomSpeed(100);
363        mZoomButtons.setOnZoomListener(
364                new ZoomButtonsController.OnZoomListener() {
365            public void onVisibilityChanged(boolean visible) {
366                if (visible) {
367                    updateZoomButtonsEnabled();
368                }
369            }
370
371            public void onZoom(boolean zoomIn) {
372                if (mZooming) return;
373
374                if (zoomIn) {
375                    if (mZoomValue < mZoomMax) {
376                        mZoomValue += mZoomStep;
377                        zoomToLevel(ZOOM_CONTINUOUS);
378                    }
379                } else {
380                    if (mZoomValue > ZOOM_MIN) {
381                        mZoomValue -= mZoomStep;
382                        zoomToLevel(ZOOM_CONTINUOUS);
383                    }
384                }
385                updateZoomButtonsEnabled();
386            }
387        });
388    }
389
390    public void onVisibilityChanged(boolean visible) {
391        // When the on-screen setting is not displayed, we show the gripper.
392        // When the on-screen setting is displayed, we hide the gripper.
393        findViewById(R.id.btn_gripper).setVisibility(
394                visible ? View.INVISIBLE : View.VISIBLE);
395    }
396
397    private void zoomToLevel(String type) {
398        if (type == null) {
399            Log.e(TAG, "Zoom type is null.");
400            return;
401        }
402        if (mZoomValue > mZoomMax) mZoomValue = mZoomMax;
403        if (mZoomValue < ZOOM_MIN) mZoomValue = ZOOM_MIN;
404
405        // If the application sets a unchanged zoom value, the driver will stuck
406        // at the zoom state. This is a work-around to ensure the state is at
407        // "stop".
408        mParameters.set(PARM_ZOOM_STATE, ZOOM_STOP);
409        mCameraDevice.setParameters(mParameters);
410
411        mParameters.set(PARM_ZOOM_TO_LEVEL, Double.toString(mZoomValue));
412        mParameters.set(PARM_ZOOM_STATE, type);
413        mCameraDevice.setParameters(mParameters);
414
415        if (ZOOM_CONTINUOUS.equals(type)) mZooming = true;
416    }
417
418    private void updateZoomButtonsEnabled() {
419        mZoomButtons.setZoomInEnabled(mZoomValue < mZoomMax);
420        mZoomButtons.setZoomOutEnabled(mZoomValue > ZOOM_MIN);
421    }
422
423    private class ZoomGestureListener extends
424            GestureDetector.SimpleOnGestureListener {
425        @Override
426        public boolean onDown(MotionEvent e) {
427            // Show zoom buttons only when preview is started and snapshot
428            // is not in progress. mZoomButtons may be null if it is not
429            // initialized.
430            if (!mPausing && isCameraIdle() && mPreviewing
431                    && mZoomButtons != null) {
432                mZoomButtons.setVisible(true);
433            }
434            return true;
435        }
436
437        @Override
438        public boolean onDoubleTap(MotionEvent e) {
439            // Perform zoom only when preview is started and snapshot is not in
440            // progress.
441            if (mPausing || !isCameraIdle() || !mPreviewing
442                    || mZoomButtons == null || mZooming) {
443                return false;
444            }
445
446            if (mZoomValue < mZoomMax) {
447                // Zoom in to the maximum.
448                while (mZoomValue < mZoomMax) {
449                    mZoomValue += ZOOM_STEP_MIN;
450                    zoomToLevel(ZOOM_IMMEDIATE);
451                    // Wait for a while so we are not changing zoom too fast.
452                    try {
453                        Thread.currentThread().sleep(5);
454                    } catch (InterruptedException ex) {
455                    }
456                }
457            } else {
458                // Zoom out to the minimum.
459                while (mZoomValue > ZOOM_MIN) {
460                    mZoomValue -= ZOOM_STEP_MIN;
461                    zoomToLevel(ZOOM_IMMEDIATE);
462                    // Wait for a while so we are not changing zoom too fast.
463                    try {
464                        Thread.currentThread().sleep(5);
465                    } catch (InterruptedException ex) {
466                    }
467                }
468            }
469            updateZoomButtonsEnabled();
470            return true;
471        }
472    }
473
474    @Override
475    public boolean dispatchTouchEvent(MotionEvent m) {
476        if (!super.dispatchTouchEvent(m) && mGestureDetector != null) {
477            return mGestureDetector.onTouchEvent(m);
478        }
479        return true;
480    }
481
482    LocationListener [] mLocationListeners = new LocationListener[] {
483            new LocationListener(LocationManager.GPS_PROVIDER),
484            new LocationListener(LocationManager.NETWORK_PROVIDER)
485    };
486
487    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
488        @Override
489        public void onReceive(Context context, Intent intent) {
490            String action = intent.getAction();
491            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
492                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
493                    || action.equals(Intent.ACTION_MEDIA_CHECKING)
494                    || action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
495                checkStorage();
496            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
497                checkStorage();
498                if (!mIsImageCaptureIntent)  {
499                    updateThumbnailButton();
500                }
501            }
502        }
503    };
504
505    private class LocationListener
506            implements android.location.LocationListener {
507        Location mLastLocation;
508        boolean mValid = false;
509        String mProvider;
510
511        public LocationListener(String provider) {
512            mProvider = provider;
513            mLastLocation = new Location(mProvider);
514        }
515
516        public void onLocationChanged(Location newLocation) {
517            if (newLocation.getLatitude() == 0.0
518                    && newLocation.getLongitude() == 0.0) {
519                // Hack to filter out 0.0,0.0 locations
520                return;
521            }
522            // If GPS is available before start camera, we won't get status
523            // update so update GPS indicator when we receive data.
524            if (mRecordLocation
525                    && LocationManager.GPS_PROVIDER.equals(mProvider)) {
526                mGpsIndicator.setVisibility(View.VISIBLE);
527            }
528            mLastLocation.set(newLocation);
529            mValid = true;
530        }
531
532        public void onProviderEnabled(String provider) {
533        }
534
535        public void onProviderDisabled(String provider) {
536            mValid = false;
537        }
538
539        public void onStatusChanged(
540                String provider, int status, Bundle extras) {
541            switch(status) {
542                case LocationProvider.OUT_OF_SERVICE:
543                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
544                    mValid = false;
545                    if (mRecordLocation &&
546                            LocationManager.GPS_PROVIDER.equals(provider)) {
547                        mGpsIndicator.setVisibility(View.INVISIBLE);
548                    }
549                    break;
550                }
551            }
552        }
553
554        public Location current() {
555            return mValid ? mLastLocation : null;
556        }
557    }
558
559    private final class OneShotPreviewCallback
560            implements android.hardware.Camera.PreviewCallback {
561        public void onPreviewFrame(byte[] data,
562                                   android.hardware.Camera camera) {
563            long now = System.currentTimeMillis();
564            if (mJpegPictureCallbackTime != 0) {
565                mJpegPictureCallbackTimeLag = now - mJpegPictureCallbackTime;
566                Log.v(TAG, "mJpegPictureCallbackTimeLag = "
567                        + mJpegPictureCallbackTimeLag + "ms");
568                mJpegPictureCallbackTime = 0;
569            } else {
570                Log.v(TAG, "Got first frame");
571            }
572        }
573    }
574
575    private final class ShutterCallback
576            implements android.hardware.Camera.ShutterCallback {
577        public void onShutter() {
578            mShutterCallbackTime = System.currentTimeMillis();
579            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
580            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
581            clearFocusState();
582        }
583    }
584
585    private final class RawPictureCallback implements PictureCallback {
586        public void onPictureTaken(
587                byte [] rawData, android.hardware.Camera camera) {
588            mRawPictureCallbackTime = System.currentTimeMillis();
589            mShutterAndRawPictureCallbackTime =
590                mRawPictureCallbackTime - mShutterCallbackTime;
591            Log.v(TAG, "mShutterAndRawPictureCallbackTime = "
592                    + mShutterAndRawPictureCallbackTime + "ms");
593        }
594    }
595
596    private final class JpegPictureCallback implements PictureCallback {
597        Location mLocation;
598
599        public JpegPictureCallback(Location loc) {
600            mLocation = loc;
601        }
602
603        public void onPictureTaken(
604                final byte [] jpegData, final android.hardware.Camera camera) {
605            if (mPausing) {
606                return;
607            }
608
609            mJpegPictureCallbackTime = System.currentTimeMillis();
610            mRawPictureAndJpegPictureCallbackTime =
611                mJpegPictureCallbackTime - mRawPictureCallbackTime;
612            Log.v(TAG, "mRawPictureAndJpegPictureCallbackTime = "
613                    + mRawPictureAndJpegPictureCallbackTime + "ms");
614            mImageCapture.storeImage(jpegData, camera, mLocation);
615
616            if (!mIsImageCaptureIntent) {
617                long delay = 1200 - (
618                        System.currentTimeMillis() - mRawPictureCallbackTime);
619                mHandler.sendEmptyMessageDelayed(
620                        RESTART_PREVIEW, Math.max(delay, 0));
621            }
622        }
623    }
624
625    private final class AutoFocusCallback
626            implements android.hardware.Camera.AutoFocusCallback {
627        public void onAutoFocus(
628                boolean focused, android.hardware.Camera camera) {
629            mFocusCallbackTime = System.currentTimeMillis();
630            mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
631            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
632            if (mFocusState == FOCUSING_SNAP_ON_FINISH) {
633                // Take the picture no matter focus succeeds or fails. No need
634                // to play the AF sound if we're about to play the shutter
635                // sound.
636                if (focused) {
637                    mFocusState = FOCUS_SUCCESS;
638                } else {
639                    mFocusState = FOCUS_FAIL;
640                }
641                mImageCapture.onSnap();
642            } else if (mFocusState == FOCUSING) {
643                // User is half-pressing the focus key. Play the focus tone.
644                // Do not take the picture now.
645                ToneGenerator tg = mFocusToneGenerator;
646                if (tg != null) {
647                    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
648                }
649                if (focused) {
650                    mFocusState = FOCUS_SUCCESS;
651                } else {
652                    mFocusState = FOCUS_FAIL;
653                }
654            } else if (mFocusState == FOCUS_NOT_STARTED) {
655                // User has released the focus key before focus completes.
656                // Do nothing.
657            }
658            updateFocusIndicator();
659        }
660    }
661
662    private final class ErrorCallback
663        implements android.hardware.Camera.ErrorCallback {
664        public void onError(int error, android.hardware.Camera camera) {
665            if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
666                 mMediaServerDied = true;
667                 Log.v(TAG, "media server died");
668            }
669        }
670    }
671
672    private final class ZoomCallback
673        implements android.hardware.Camera.ZoomCallback {
674        public void onZoomUpdate(int zoomLevel,
675                                 android.hardware.Camera camera) {
676            mZoomValue = (double) zoomLevel / 1000;
677            mZooming = false;
678        }
679    }
680
681    private class ImageCapture {
682
683        private boolean mCancel = false;
684
685        private Uri mLastContentUri;
686
687        Bitmap mCaptureOnlyBitmap;
688
689        private void storeImage(byte[] data, Location loc) {
690            try {
691                long dateTaken = System.currentTimeMillis();
692                String name = createName(dateTaken) + ".jpg";
693                mLastContentUri = ImageManager.addImage(
694                        mContentResolver,
695                        name,
696                        dateTaken,
697                        loc, // location for the database goes here
698                        0, // the dsp will use the right orientation so
699                           // don't "double set it"
700                        ImageManager.CAMERA_IMAGE_BUCKET_NAME,
701                        name);
702                if (mLastContentUri == null) {
703                    // this means we got an error
704                    mCancel = true;
705                }
706                if (!mCancel) {
707                    ImageManager.storeImage(
708                            mLastContentUri, mContentResolver,
709                            0, null, data);
710                    ImageManager.setImageSize(mContentResolver, mLastContentUri,
711                            new File(ImageManager.CAMERA_IMAGE_BUCKET_NAME,
712                            name).length());
713                }
714            } catch (Exception ex) {
715                Log.e(TAG, "Exception while compressing image.", ex);
716            }
717        }
718
719        public void storeImage(final byte[] data,
720                android.hardware.Camera camera, Location loc) {
721            if (!mIsImageCaptureIntent) {
722                storeImage(data, loc);
723                sendBroadcast(new Intent(
724                        "com.android.camera.NEW_PICTURE", mLastContentUri));
725                setLastPictureThumb(data, mImageCapture.getLastCaptureUri());
726                mThumbController.updateDisplayIfNeeded();
727            } else {
728                BitmapFactory.Options options = new BitmapFactory.Options();
729                options.inSampleSize = 4;
730                mCaptureOnlyBitmap = BitmapFactory.decodeByteArray(
731                        data, 0, data.length, options);
732                showPostCaptureAlert();
733            }
734        }
735
736        /**
737         * Initiate the capture of an image.
738         */
739        public void initiate() {
740            if (mCameraDevice == null) {
741                return;
742            }
743
744            mCancel = false;
745
746            capture();
747        }
748
749        public Uri getLastCaptureUri() {
750            return mLastContentUri;
751        }
752
753        public Bitmap getLastBitmap() {
754            return mCaptureOnlyBitmap;
755        }
756
757        private void capture() {
758            mCaptureOnlyBitmap = null;
759
760            // Set rotation.
761            int orientation = mLastOrientation;
762            if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
763                orientation += 90;
764            }
765            orientation = ImageManager.roundOrientation(orientation);
766            Log.v(TAG, "mLastOrientation = " + mLastOrientation
767                    + ", orientation = " + orientation);
768            mParameters.setRotation(orientation);
769
770            // Clear previous GPS location from the parameters.
771            mParameters.removeGpsData();
772
773            // Set GPS location.
774            Location loc = mRecordLocation ? getCurrentLocation() : null;
775            if (loc != null) {
776                double lat = loc.getLatitude();
777                double lon = loc.getLongitude();
778                boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
779
780                if (hasLatLon) {
781                    mParameters.setGpsLatitude(lat);
782                    mParameters.setGpsLongitude(lon);
783                    if (loc.hasAltitude()) {
784                        mParameters.setGpsAltitude(loc.getAltitude());
785                    } else {
786                        // for NETWORK_PROVIDER location provider, we may have
787                        // no altitude information, but the driver needs it, so
788                        // we fake one.
789                        mParameters.setGpsAltitude(0);
790                    }
791                    if (loc.getTime() != 0) {
792                        // Location.getTime() is UTC in milliseconds.
793                        // gps-timestamp is UTC in seconds.
794                        long utcTimeSeconds = loc.getTime() / 1000;
795                        mParameters.setGpsTimestamp(utcTimeSeconds);
796                    }
797                } else {
798                    loc = null;
799                }
800            }
801
802            mCameraDevice.setParameters(mParameters);
803
804            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
805                    new JpegPictureCallback(loc));
806            mPreviewing = false;
807        }
808
809        public void onSnap() {
810            // If we are already in the middle of taking a snapshot then ignore.
811            if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS) {
812                return;
813            }
814            mCaptureStartTime = System.currentTimeMillis();
815
816            // Don't check the filesystem here, we can't afford the latency.
817            // Instead, check the cached value which was calculated when the
818            // preview was restarted.
819            if (mPicturesRemaining < 1) {
820                updateStorageHint(mPicturesRemaining);
821                return;
822            }
823
824            mStatus = SNAPSHOT_IN_PROGRESS;
825
826            mImageCapture.initiate();
827        }
828
829        private void clearLastBitmap() {
830            if (mCaptureOnlyBitmap != null) {
831                mCaptureOnlyBitmap.recycle();
832                mCaptureOnlyBitmap = null;
833            }
834        }
835    }
836
837    private void setLastPictureThumb(byte[] data, Uri uri) {
838        BitmapFactory.Options options = new BitmapFactory.Options();
839        options.inSampleSize = 16;
840        Bitmap lastPictureThumb =
841                BitmapFactory.decodeByteArray(data, 0, data.length, options);
842        mThumbController.setData(uri, lastPictureThumb);
843    }
844
845    private static String createName(long dateTaken) {
846        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
847    }
848
849    @Override
850    public void onCreate(Bundle icicle) {
851        super.onCreate(icicle);
852
853        Window win = getWindow();
854        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
855        setContentView(R.layout.camera);
856        mSurfaceView = (VideoPreview) findViewById(R.id.camera_preview);
857        mViewFinderWidth = mSurfaceView.getLayoutParams().width;
858        mViewFinderHeight = mSurfaceView.getLayoutParams().height;
859        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
860        mPreferences.registerOnSharedPreferenceChangeListener(this);
861
862        /*
863         * To reduce startup time, we start the preview in another thread.
864         * We make sure the preview is started at the end of onCreate.
865         */
866        Thread startPreviewThread = new Thread(new Runnable() {
867            public void run() {
868                try {
869                    mStartPreviewFail = false;
870                    startPreview();
871                } catch (CameraHardwareException e) {
872                    // In eng build, we throw the exception so that test tool
873                    // can detect it and report it
874                    if ("eng".equals(Build.TYPE)) {
875                        throw new RuntimeException(e);
876                    }
877                    mStartPreviewFail = true;
878                }
879            }
880        });
881        startPreviewThread.start();
882
883        // don't set mSurfaceHolder here. We have it set ONLY within
884        // surfaceChanged / surfaceDestroyed, other parts of the code
885        // assume that when it is set, the surface is also set.
886        SurfaceHolder holder = mSurfaceView.getHolder();
887        holder.addCallback(this);
888        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
889
890        mIsImageCaptureIntent = isImageCaptureIntent();
891        LayoutInflater inflater = getLayoutInflater();
892
893        ViewGroup rootView = (ViewGroup) findViewById(R.id.camera);
894        if (mIsImageCaptureIntent) {
895            View controlBar = inflater.inflate(
896                    R.layout.attach_camera_control, rootView);
897            controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this);
898            controlBar.findViewById(R.id.btn_retake).setOnClickListener(this);
899            controlBar.findViewById(R.id.btn_done).setOnClickListener(this);
900        } else {
901            inflater.inflate(R.layout.camera_control, rootView);
902            mSwitcher = ((Switcher) findViewById(R.id.camera_switch));
903            mSwitcher.setOnSwitchListener(this);
904            mSwitcher.addTouchView(findViewById(R.id.camera_switch_set));
905        }
906        findViewById(R.id.btn_gripper)
907                .setOnTouchListener(new GripperTouchListener());
908
909        // Make sure preview is started.
910        try {
911            startPreviewThread.join();
912            if (mStartPreviewFail) {
913                showCameraErrorAndFinish();
914                return;
915            }
916        } catch (InterruptedException ex) {
917            // ignore
918        }
919
920        // Resize mVideoPreview to the right aspect ratio.
921        resizeForPreviewAspectRatio(mSurfaceView);
922    }
923
924    private class GripperTouchListener implements View.OnTouchListener {
925        public boolean onTouch(View view, MotionEvent event) {
926            switch (event.getAction()) {
927                case MotionEvent.ACTION_DOWN:
928                    return true;
929                case MotionEvent.ACTION_UP:
930                    showOnScreenSettings();
931                    return true;
932            }
933            return false;
934        }
935    }
936
937    @Override
938    public void onStart() {
939        super.onStart();
940        if (!mIsImageCaptureIntent) {
941            mSwitcher.setSwitch(SWITCH_CAMERA);
942        }
943    }
944
945    private void checkStorage() {
946        if (ImageManager.isMediaScannerScanning(getContentResolver())) {
947            mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR;
948        } else {
949            calculatePicturesRemaining();
950        }
951        updateStorageHint(mPicturesRemaining);
952    }
953
954    private void showOnScreenSettings() {
955        if (mSettings == null) {
956            mSettings = new OnScreenSettings(
957                    findViewById(R.id.camera_preview));
958            CameraSettings helper = new CameraSettings(this, mParameters);
959            mSettings.setPreferenceScreen(helper
960                    .getPreferenceScreen(R.xml.camera_preferences));
961            mSettings.setOnVisibilityChangedListener(this);
962        }
963        mSettings.expandPanel();
964    }
965
966    public void onClick(View v) {
967        switch (v.getId()) {
968            case R.id.btn_retake:
969                hidePostCaptureAlert();
970                restartPreview();
971                break;
972            case R.id.review_thumbnail:
973                if (isCameraIdle()) {
974                    viewLastImage();
975                }
976                break;
977            case R.id.btn_done:
978                doAttach();
979                break;
980            case R.id.btn_cancel:
981                doCancel();
982        }
983    }
984
985    private void doAttach() {
986        if (mPausing) {
987            return;
988        }
989        Bitmap bitmap = mImageCapture.getLastBitmap();
990
991        String cropValue = null;
992        Uri saveUri = null;
993
994        Bundle myExtras = getIntent().getExtras();
995        if (myExtras != null) {
996            saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
997            cropValue = myExtras.getString("crop");
998        }
999
1000
1001        if (cropValue == null) {
1002            // First handle the no crop case -- just return the value.  If the
1003            // caller specifies a "save uri" then write the data to it's
1004            // stream. Otherwise, pass back a scaled down version of the bitmap
1005            // directly in the extras.
1006            if (saveUri != null) {
1007                OutputStream outputStream = null;
1008                try {
1009                    outputStream = mContentResolver.openOutputStream(saveUri);
1010                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75,
1011                            outputStream);
1012                    outputStream.close();
1013
1014                    setResult(RESULT_OK);
1015                    finish();
1016                } catch (IOException ex) {
1017                    // ignore exception
1018                } finally {
1019                    if (outputStream != null) {
1020                        try {
1021                            outputStream.close();
1022                        } catch (IOException ex) {
1023                            // ignore exception
1024                        }
1025                    }
1026                }
1027            } else {
1028                float scale = .5F;
1029                Matrix m = new Matrix();
1030                m.setScale(scale, scale);
1031
1032                bitmap = Bitmap.createBitmap(bitmap, 0, 0,
1033                        bitmap.getWidth(),
1034                        bitmap.getHeight(),
1035                        m, true);
1036
1037                setResult(RESULT_OK,
1038                        new Intent("inline-data").putExtra("data", bitmap));
1039                finish();
1040            }
1041        } else {
1042            // Save the image to a temp file and invoke the cropper
1043            Uri tempUri = null;
1044            FileOutputStream tempStream = null;
1045            try {
1046                File path = getFileStreamPath(sTempCropFilename);
1047                path.delete();
1048                tempStream = openFileOutput(sTempCropFilename, 0);
1049                bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream);
1050                tempStream.close();
1051                tempUri = Uri.fromFile(path);
1052            } catch (FileNotFoundException ex) {
1053                setResult(Activity.RESULT_CANCELED);
1054                finish();
1055                return;
1056            } catch (IOException ex) {
1057                setResult(Activity.RESULT_CANCELED);
1058                finish();
1059                return;
1060            } finally {
1061                if (tempStream != null) {
1062                    try {
1063                        tempStream.close();
1064                    } catch (IOException ex) {
1065                        // ignore exception
1066                    }
1067                }
1068            }
1069
1070            Bundle newExtras = new Bundle();
1071            if (cropValue.equals("circle")) {
1072                newExtras.putString("circleCrop", "true");
1073            }
1074            if (saveUri != null) {
1075                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri);
1076            } else {
1077                newExtras.putBoolean("return-data", true);
1078            }
1079
1080            Intent cropIntent = new Intent();
1081            cropIntent.setClass(this, CropImage.class);
1082            cropIntent.setData(tempUri);
1083            cropIntent.putExtras(newExtras);
1084
1085            startActivityForResult(cropIntent, CROP_MSG);
1086        }
1087    }
1088
1089    private void doCancel() {
1090        setResult(RESULT_CANCELED, new Intent());
1091        finish();
1092    }
1093
1094    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
1095        if (mPausing) {
1096            return;
1097        }
1098        switch (button.getId()) {
1099            case R.id.shutter_button:
1100                doFocus(pressed);
1101                break;
1102        }
1103    }
1104
1105    public void onShutterButtonClick(ShutterButton button) {
1106        if (mPausing) {
1107            return;
1108        }
1109        switch (button.getId()) {
1110            case R.id.shutter_button:
1111                doSnap();
1112                break;
1113        }
1114    }
1115
1116    private OnScreenHint mStorageHint;
1117
1118    private void updateStorageHint(int remaining) {
1119        String noStorageText = null;
1120
1121        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
1122            String state = Environment.getExternalStorageState();
1123            if (state == Environment.MEDIA_CHECKING ||
1124                    ImageManager.isMediaScannerScanning(getContentResolver())) {
1125                noStorageText = getString(R.string.preparing_sd);
1126            } else {
1127                noStorageText = getString(R.string.no_storage);
1128            }
1129        } else if (remaining < 1) {
1130            noStorageText = getString(R.string.not_enough_space);
1131        }
1132
1133        if (noStorageText != null) {
1134            if (mStorageHint == null) {
1135                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1136            } else {
1137                mStorageHint.setText(noStorageText);
1138            }
1139            mStorageHint.show();
1140        } else if (mStorageHint != null) {
1141            mStorageHint.cancel();
1142            mStorageHint = null;
1143        }
1144    }
1145
1146    private void installIntentFilter() {
1147        // install an intent filter to receive SD card related events.
1148        IntentFilter intentFilter =
1149                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1150        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1151        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
1152        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1153        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1154        intentFilter.addDataScheme("file");
1155        registerReceiver(mReceiver, intentFilter);
1156        mDidRegister = true;
1157    }
1158
1159    private void initializeFocusTone() {
1160        // Initialize focus tone generator.
1161        try {
1162            mFocusToneGenerator = new ToneGenerator(
1163                    AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1164        } catch (Throwable ex) {
1165            Log.w(TAG, "Exception caught while creating tone generator: ", ex);
1166            mFocusToneGenerator = null;
1167        }
1168    }
1169
1170    private void readPreference() {
1171        mRecordLocation = mPreferences.getBoolean(
1172                "pref_camera_recordlocation_key", false);
1173        mFocusMode = mPreferences.getString(
1174                CameraSettings.KEY_FOCUS_MODE,
1175                getString(R.string.pref_camera_focusmode_default));
1176    }
1177
1178    @Override
1179    public void onResume() {
1180        super.onResume();
1181
1182        mPausing = false;
1183        mJpegPictureCallbackTime = 0;
1184        mImageCapture = new ImageCapture();
1185
1186        // Start the preview if it is not started.
1187        if (!mPreviewing && !mStartPreviewFail) {
1188            try {
1189                startPreview();
1190            } catch (CameraHardwareException e) {
1191                showCameraErrorAndFinish();
1192                return;
1193            }
1194        }
1195
1196        if (mSurfaceHolder != null) {
1197            // If first time initialization is not finished, put it in the
1198            // message queue.
1199            if (!mFirstTimeInitialized) {
1200                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1201            } else {
1202                initializeSecondTime();
1203            }
1204        }
1205        keepScreenOnAwhile();
1206    }
1207
1208    private static ImageManager.DataLocation dataLocation() {
1209        return ImageManager.DataLocation.EXTERNAL;
1210    }
1211
1212    @Override
1213    protected void onPause() {
1214        mPausing = true;
1215        stopPreview();
1216        // Close the camera now because other activities may need to use it.
1217        closeCamera();
1218        resetScreenOn();
1219
1220        if (mSettings != null && mSettings.isVisible()) {
1221            mSettings.setVisible(false);
1222        }
1223
1224        if (mFirstTimeInitialized) {
1225            mOrientationListener.disable();
1226            mGpsIndicator.setVisibility(View.INVISIBLE);
1227            if (!mIsImageCaptureIntent) {
1228                mThumbController.storeData(
1229                        ImageManager.getLastImageThumbPath());
1230            }
1231            hidePostCaptureAlert();
1232        }
1233
1234        if (mDidRegister) {
1235            unregisterReceiver(mReceiver);
1236            mDidRegister = false;
1237        }
1238        stopReceivingLocationUpdates();
1239
1240        if (mFocusToneGenerator != null) {
1241            mFocusToneGenerator.release();
1242            mFocusToneGenerator = null;
1243        }
1244
1245        if (mStorageHint != null) {
1246            mStorageHint.cancel();
1247            mStorageHint = null;
1248        }
1249
1250        // If we are in an image capture intent and has taken
1251        // a picture, we just clear it in onPause.
1252        mImageCapture.clearLastBitmap();
1253        mImageCapture = null;
1254
1255        // This is necessary to make the ZoomButtonsController unregister
1256        // its configuration change receiver.
1257        if (mZoomButtons != null) {
1258            mZoomButtons.setVisible(false);
1259        }
1260
1261        // Remove the messages in the event queue.
1262        mHandler.removeMessages(RESTART_PREVIEW);
1263        mHandler.removeMessages(FIRST_TIME_INIT);
1264
1265        super.onPause();
1266    }
1267
1268    @Override
1269    protected void onActivityResult(
1270            int requestCode, int resultCode, Intent data) {
1271        switch (requestCode) {
1272            case CROP_MSG: {
1273                Intent intent = new Intent();
1274                if (data != null) {
1275                    Bundle extras = data.getExtras();
1276                    if (extras != null) {
1277                        intent.putExtras(extras);
1278                    }
1279                }
1280                setResult(resultCode, intent);
1281                finish();
1282
1283                File path = getFileStreamPath(sTempCropFilename);
1284                path.delete();
1285
1286                break;
1287            }
1288        }
1289    }
1290
1291    private boolean canTakePicture() {
1292        return isCameraIdle() && mPreviewing && (mPicturesRemaining > 0);
1293    }
1294
1295    private void autoFocus() {
1296        // Initiate autofocus only when preview is started and snapshot is not
1297        // in progress.
1298        if (canTakePicture()) {
1299            Log.v(TAG, "Start autofocus.");
1300            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1301            mFocusStartTime = System.currentTimeMillis();
1302            mFocusState = FOCUSING;
1303            updateFocusIndicator();
1304            mCameraDevice.autoFocus(mAutoFocusCallback);
1305        }
1306    }
1307
1308    private void cancelAutoFocus() {
1309        // User releases half-pressed focus key.
1310        if (mFocusState == FOCUSING || mFocusState == FOCUS_SUCCESS
1311                || mFocusState == FOCUS_FAIL) {
1312            Log.v(TAG, "Cancel autofocus.");
1313            mCameraDevice.cancelAutoFocus();
1314        }
1315        if (mFocusState != FOCUSING_SNAP_ON_FINISH) {
1316            clearFocusState();
1317        }
1318    }
1319
1320    private void clearFocusState() {
1321        mFocusState = FOCUS_NOT_STARTED;
1322        updateFocusIndicator();
1323    }
1324
1325    private void updateFocusIndicator() {
1326        if (mFocusRectangle == null) return;
1327
1328        if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) {
1329            mFocusRectangle.showStart();
1330        } else if (mFocusState == FOCUS_SUCCESS) {
1331            mFocusRectangle.showSuccess();
1332        } else if (mFocusState == FOCUS_FAIL) {
1333            mFocusRectangle.showFail();
1334        } else {
1335            mFocusRectangle.clear();
1336        }
1337    }
1338
1339    @Override
1340    public boolean onKeyDown(int keyCode, KeyEvent event) {
1341        switch (keyCode) {
1342            case KeyEvent.KEYCODE_BACK:
1343                if (!isCameraIdle()) {
1344                    // ignore backs while we're taking a picture
1345                    return true;
1346                }
1347                break;
1348            case KeyEvent.KEYCODE_FOCUS:
1349                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1350                    doFocus(true);
1351                }
1352                return true;
1353            case KeyEvent.KEYCODE_CAMERA:
1354                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1355                    doSnap();
1356                }
1357                return true;
1358            case KeyEvent.KEYCODE_DPAD_CENTER:
1359                // If we get a dpad center event without any focused view, move
1360                // the focus to the shutter button and press it.
1361                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1362                    // Start auto-focus immediately to reduce shutter lag. After
1363                    // the shutter button gets the focus, doFocus() will be
1364                    // called again but it is fine.
1365                    doFocus(true);
1366                    if (mShutterButton.isInTouchMode()) {
1367                        mShutterButton.requestFocusFromTouch();
1368                    } else {
1369                        mShutterButton.requestFocus();
1370                    }
1371                    mShutterButton.setPressed(true);
1372                }
1373                return true;
1374        }
1375
1376        return super.onKeyDown(keyCode, event);
1377    }
1378
1379    @Override
1380    public boolean onKeyUp(int keyCode, KeyEvent event) {
1381        switch (keyCode) {
1382            case KeyEvent.KEYCODE_FOCUS:
1383                if (mFirstTimeInitialized) {
1384                    doFocus(false);
1385                }
1386                return true;
1387        }
1388        return super.onKeyUp(keyCode, event);
1389    }
1390
1391    private void doSnap() {
1392        // If the user has half-pressed the shutter and focus is completed, we
1393        // can take the photo right away. If the focus mode is infinity, we can
1394        // also take the photo.
1395        if (mFocusMode.equals(getString(
1396                R.string.pref_camera_focusmode_value_infinity))
1397                || (mFocusState == FOCUS_SUCCESS
1398                || mFocusState == FOCUS_FAIL)) {
1399            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1400            mImageCapture.onSnap();
1401        } else if (mFocusState == FOCUSING) {
1402            // Half pressing the shutter (i.e. the focus button event) will
1403            // already have requested AF for us, so just request capture on
1404            // focus here.
1405            mFocusState = FOCUSING_SNAP_ON_FINISH;
1406        } else if (mFocusState == FOCUS_NOT_STARTED) {
1407            // Focus key down event is dropped for some reasons. Just ignore.
1408        }
1409    }
1410
1411    private void doFocus(boolean pressed) {
1412        // Do the focus if the mode is auto. No focus needed in infinity mode.
1413        if (mFocusMode.equals(getString(
1414                R.string.pref_camera_focusmode_value_auto))) {
1415            if (pressed) {  // Focus key down.
1416                autoFocus();
1417            } else {  // Focus key up.
1418                cancelAutoFocus();
1419            }
1420        }
1421    }
1422
1423    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1424        // Make sure we have a surface in the holder before proceeding.
1425        if (holder.getSurface() == null) {
1426            Log.d(TAG, "holder.getSurface() == null");
1427            return;
1428        }
1429
1430        // The mCameraDevice will be null if it fails to connect to the camera
1431        // hardware. In this case we will show a dialog and then finish the
1432        // activity, so it's OK to ignore it.
1433        if (mCameraDevice == null) return;
1434
1435        mSurfaceHolder = holder;
1436        mViewFinderWidth = w;
1437        mViewFinderHeight = h;
1438
1439        // Sometimes surfaceChanged is called after onPause. Ignore it.
1440        if (mPausing || isFinishing()) return;
1441
1442        // Set preview display if the surface is being created. Preview was
1443        // already started.
1444        if (holder.isCreating()) {
1445            setPreviewDisplay(holder);
1446        }
1447
1448        // If first time initialization is not finished, send a message to do
1449        // it later. We want to finish surfaceChanged as soon as possible to let
1450        // user see preview first.
1451        if (!mFirstTimeInitialized) {
1452            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1453        } else {
1454            initializeSecondTime();
1455        }
1456    }
1457
1458    public void surfaceCreated(SurfaceHolder holder) {
1459    }
1460
1461    public void surfaceDestroyed(SurfaceHolder holder) {
1462        stopPreview();
1463        mSurfaceHolder = null;
1464    }
1465
1466    private void closeCamera() {
1467        if (mCameraDevice != null) {
1468            CameraHolder.instance().release();
1469            mCameraDevice = null;
1470            mPreviewing = false;
1471        }
1472    }
1473
1474    private void ensureCameraDevice() throws CameraHardwareException {
1475        if (mCameraDevice == null) {
1476            mCameraDevice = CameraHolder.instance().open();
1477        }
1478    }
1479
1480    private void updateLastImage() {
1481        IImageList list = ImageManager.makeImageList(
1482            mContentResolver,
1483            dataLocation(),
1484            ImageManager.INCLUDE_IMAGES,
1485            ImageManager.SORT_ASCENDING,
1486            ImageManager.CAMERA_IMAGE_BUCKET_ID);
1487        int count = list.getCount();
1488        if (count > 0) {
1489            IImage image = list.getImageAt(count - 1);
1490            Uri uri = image.fullSizeImageUri();
1491            mThumbController.setData(uri, image.miniThumbBitmap());
1492        } else {
1493            mThumbController.setData(null, null);
1494        }
1495        list.close();
1496    }
1497
1498    private void showCameraErrorAndFinish() {
1499        Resources ress = getResources();
1500        Util.showFatalErrorAndFinish(Camera.this,
1501                ress.getString(R.string.camera_error_title),
1502                ress.getString(R.string.cannot_connect_camera));
1503    }
1504
1505    private void restartPreview() {
1506        // make sure the surfaceview fills the whole screen when previewing
1507        mSurfaceView.setAspectRatio(VideoPreview.DONT_CARE);
1508        try {
1509            startPreview();
1510        } catch (CameraHardwareException e) {
1511            showCameraErrorAndFinish();
1512            return;
1513        }
1514
1515        // Calculate this in advance of each shot so we don't add to shutter
1516        // latency. It's true that someone else could write to the SD card in
1517        // the mean time and fill it, but that could have happened between the
1518        // shutter press and saving the JPEG too.
1519        calculatePicturesRemaining();
1520    }
1521
1522    private void setPreviewDisplay(SurfaceHolder holder) {
1523        try {
1524            mCameraDevice.setPreviewDisplay(holder);
1525        } catch (Throwable ex) {
1526            closeCamera();
1527            throw new RuntimeException("setPreviewDisplay failed", ex);
1528        }
1529    }
1530
1531    private void startPreview() throws CameraHardwareException {
1532        if (mPausing || isFinishing()) return;
1533
1534        ensureCameraDevice();
1535
1536        // If we're previewing already, stop the preview first (this will blank
1537        // the screen).
1538        if (mPreviewing) stopPreview();
1539
1540        setPreviewDisplay(mSurfaceHolder);
1541
1542        setCameraParameter();
1543
1544        final long wallTimeStart = SystemClock.elapsedRealtime();
1545        final long threadTimeStart = Debug.threadCpuTimeNanos();
1546
1547        // Set one shot preview callback for latency measurement.
1548        mCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
1549        mCameraDevice.setErrorCallback(mErrorCallback);
1550
1551        try {
1552            Log.v(TAG, "startPreview");
1553            mCameraDevice.startPreview();
1554        } catch (Throwable ex) {
1555            closeCamera();
1556            throw new RuntimeException("startPreview failed", ex);
1557        }
1558        mPreviewing = true;
1559        mStatus = IDLE;
1560
1561        long threadTimeEnd = Debug.threadCpuTimeNanos();
1562        long wallTimeEnd = SystemClock.elapsedRealtime();
1563        if ((wallTimeEnd - wallTimeStart) > 3000) {
1564            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart)
1565                    + " ms. Thread time was"
1566                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
1567        }
1568    }
1569
1570    private void stopPreview() {
1571        if (mCameraDevice != null && mPreviewing) {
1572            Log.v(TAG, "stopPreview");
1573            mCameraDevice.stopPreview();
1574        }
1575        mPreviewing = false;
1576        // If auto focus was in progress, it would have been canceled.
1577        clearFocusState();
1578    }
1579
1580    private void resizeForPreviewAspectRatio(View v) {
1581        ViewGroup.LayoutParams params;
1582        params = v.getLayoutParams();
1583        Size size = mParameters.getPreviewSize();
1584        params.width = (int) (params.height * size.width / size.height);
1585        Log.v(TAG, "resize to " + params.width + "x" + params.height);
1586        v.setLayoutParams(params);
1587    }
1588
1589    private Size getOptimalPreviewSize(List<Size> sizes) {
1590        Size optimalSize = null;
1591        if (sizes != null) {
1592            optimalSize = sizes.get(0);
1593            for (int i = 1; i < sizes.size(); i++) {
1594                if (Math.abs(sizes.get(i).height - mViewFinderHeight) <
1595                        Math.abs(optimalSize.height - mViewFinderHeight)) {
1596                    optimalSize = sizes.get(i);
1597                }
1598            }
1599            Log.v(TAG, "Optimal preview size is " + optimalSize.width + "x"
1600                    + optimalSize.height);
1601        }
1602        return optimalSize;
1603    }
1604
1605    private void setCameraParameter() {
1606        mParameters = mCameraDevice.getParameters();
1607
1608        // Reset preview frame rate to the maximum because it may be lowered by
1609        // video camera application.
1610        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1611        if (frameRates != null) {
1612            Integer max = Collections.max(frameRates);
1613            mParameters.setPreviewFrameRate(max);
1614        }
1615
1616        // Set a preview size that is closest to the viewfinder height.
1617        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1618        Size optimalSize = getOptimalPreviewSize(sizes);
1619        if (optimalSize != null) {
1620            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1621        }
1622
1623        // Set picture size.
1624        String pictureSize = mPreferences.getString(
1625                CameraSettings.KEY_PICTURE_SIZE,
1626                getString(R.string.pref_camera_picturesize_default));
1627        setCameraPictureSizeIfSupported(pictureSize);
1628
1629        // Set JPEG quality.
1630        String jpegQuality = mPreferences.getString(
1631                CameraSettings.KEY_JPEG_QUALITY,
1632                getString(R.string.pref_camera_jpegquality_default));
1633        mParameters.setJpegQuality(Integer.parseInt(jpegQuality));
1634
1635        // Set flash mode.
1636        if (mParameters.getSupportedFlashModes() != null) {
1637            String flashMode = mPreferences.getString(
1638                    CameraSettings.KEY_FLASH_MODE, "auto");
1639            mParameters.setFlashMode(flashMode);
1640        }
1641
1642        // Set white balance parameter.
1643        if (mParameters.getSupportedWhiteBalance() != null) {
1644            String whiteBalance = mPreferences.getString(
1645                    CameraSettings.KEY_WHITE_BALANCE,
1646                    getString(R.string.pref_camera_whitebalance_default));
1647            mParameters.setWhiteBalance(whiteBalance);
1648        }
1649
1650        // Set color effect parameter.
1651        if (mParameters.getSupportedColorEffects() != null) {
1652            String colorEffect = mPreferences.getString(
1653                    CameraSettings.KEY_COLOR_EFFECT,
1654                    getString(R.string.pref_camera_coloreffect_default));
1655            mParameters.setColorEffect(colorEffect);
1656        }
1657
1658        mCameraDevice.setParameters(mParameters);
1659    }
1660
1661    private void gotoGallery() {
1662        MenuHelper.gotoCameraImageGallery(this);
1663    }
1664
1665    private void viewLastImage() {
1666        if (mThumbController.isUriValid()) {
1667            Uri targetUri = mThumbController.getUri();
1668            targetUri = targetUri.buildUpon().appendQueryParameter(
1669                    "bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
1670            Intent intent = new Intent(this, ReviewImage.class);
1671            intent.setData(targetUri);
1672            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
1673            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
1674            intent.putExtra("com.android.camera.ReviewMode", true);
1675            try {
1676                startActivity(intent);
1677            } catch (ActivityNotFoundException ex) {
1678                Log.e(TAG, "review image fail", ex);
1679            }
1680        } else {
1681            Log.e(TAG, "Can't view last image.");
1682        }
1683    }
1684
1685    private void startReceivingLocationUpdates() {
1686        if (mLocationManager != null) {
1687            try {
1688                mLocationManager.requestLocationUpdates(
1689                        LocationManager.NETWORK_PROVIDER,
1690                        1000,
1691                        0F,
1692                        mLocationListeners[1]);
1693            } catch (java.lang.SecurityException ex) {
1694                Log.i(TAG, "fail to request location update, ignore", ex);
1695            } catch (IllegalArgumentException ex) {
1696                Log.d(TAG, "provider does not exist " + ex.getMessage());
1697            }
1698            try {
1699                mLocationManager.requestLocationUpdates(
1700                        LocationManager.GPS_PROVIDER,
1701                        1000,
1702                        0F,
1703                        mLocationListeners[0]);
1704            } catch (java.lang.SecurityException ex) {
1705                Log.i(TAG, "fail to request location update, ignore", ex);
1706            } catch (IllegalArgumentException ex) {
1707                Log.d(TAG, "provider does not exist " + ex.getMessage());
1708            }
1709        }
1710    }
1711
1712    private void stopReceivingLocationUpdates() {
1713        if (mLocationManager != null) {
1714            for (int i = 0; i < mLocationListeners.length; i++) {
1715                try {
1716                    mLocationManager.removeUpdates(mLocationListeners[i]);
1717                } catch (Exception ex) {
1718                    Log.i(TAG, "fail to remove location listners, ignore", ex);
1719                }
1720            }
1721        }
1722    }
1723
1724    private Location getCurrentLocation() {
1725        // go in best to worst order
1726        for (int i = 0; i < mLocationListeners.length; i++) {
1727            Location l = mLocationListeners[i].current();
1728            if (l != null) return l;
1729        }
1730        return null;
1731    }
1732
1733    private boolean isCameraIdle() {
1734        return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED;
1735    }
1736
1737    private boolean isImageCaptureIntent() {
1738        String action = getIntent().getAction();
1739        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1740    }
1741
1742    private void showPostCaptureAlert() {
1743        if (mIsImageCaptureIntent) {
1744            findViewById(R.id.shutter_button).setVisibility(View.INVISIBLE);
1745            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1746            for (int id : pickIds) {
1747                View button = findViewById(id);
1748                ((View) button.getParent()).setVisibility(View.VISIBLE);
1749            }
1750        }
1751    }
1752
1753    private void hidePostCaptureAlert() {
1754        if (mIsImageCaptureIntent) {
1755            findViewById(R.id.shutter_button).setVisibility(View.VISIBLE);
1756            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1757            for (int id : pickIds) {
1758                View button = findViewById(id);
1759                ((View) button.getParent()).setVisibility(View.GONE);
1760            }
1761        }
1762    }
1763
1764    private int calculatePicturesRemaining() {
1765        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
1766        return mPicturesRemaining;
1767    }
1768
1769    @Override
1770    public boolean onPrepareOptionsMenu(Menu menu) {
1771        super.onPrepareOptionsMenu(menu);
1772        // Only show the menu when camera is idle.
1773        for (int i = 0; i < menu.size(); i++) {
1774            menu.getItem(i).setVisible(isCameraIdle());
1775        }
1776
1777        return true;
1778    }
1779
1780    @Override
1781    public boolean onCreateOptionsMenu(Menu menu) {
1782        super.onCreateOptionsMenu(menu);
1783
1784        if (mIsImageCaptureIntent) {
1785            // No options menu for attach mode.
1786            return false;
1787        } else {
1788            addBaseMenuItems(menu);
1789        }
1790        return true;
1791    }
1792
1793    private void addBaseMenuItems(Menu menu) {
1794        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1795                MenuHelper.POSITION_GOTO_GALLERY,
1796                R.string.camera_gallery_photos_text)
1797                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1798            public boolean onMenuItemClick(MenuItem item) {
1799                gotoGallery();
1800                return true;
1801            }
1802        });
1803        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1804        mGalleryItems.add(gallery);
1805
1806        MenuItem item = menu.add(Menu.NONE, Menu.NONE,
1807                MenuHelper.POSITION_CAMERA_SETTING, R.string.settings)
1808                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1809            public boolean onMenuItemClick(MenuItem item) {
1810                showOnScreenSettings();
1811                return true;
1812            }
1813        });
1814        item.setIcon(android.R.drawable.ic_menu_preferences);
1815    }
1816
1817    public boolean onSwitchChanged(Switcher source, boolean onOff) {
1818        if (onOff == SWITCH_VIDEO) {
1819            if (!isCameraIdle()) return false;
1820            MenuHelper.gotoVideoMode(this);
1821            finish();
1822        }
1823        return true;
1824    }
1825
1826    public void onFlashModeChanged(String modeString) {
1827        SharedPreferences.Editor editor = mPreferences.edit();
1828        editor.putString(CameraSettings.KEY_FLASH_MODE, modeString);
1829        editor.commit();
1830        if (!mPausing) {
1831            mParameters.setFlashMode(modeString);
1832            mCameraDevice.setParameters(mParameters);
1833        }
1834    }
1835
1836    private void setCameraPictureSizeIfSupported(String sizeString) {
1837        List<Size> pictureSizes = mParameters.getSupportedPictureSizes();
1838        if (pictureSizes != null) {
1839            int index = sizeString.indexOf('x');
1840            int width = Integer.parseInt(sizeString.substring(0, index));
1841            int height = Integer.parseInt(sizeString.substring(index + 1));
1842            for (Size size: pictureSizes) {
1843                if (size.width == width && size.height == height) {
1844                    mParameters.setPictureSize(width, height);
1845                    break;
1846                }
1847            }
1848        }
1849    }
1850
1851    public void onSharedPreferenceChanged(
1852            SharedPreferences preferences, String key) {
1853        // ignore the events after "onPause()"
1854        if (mPausing) return;
1855
1856        if (CameraSettings.KEY_FLASH_MODE.equals(key)) {
1857            mParameters.setFlashMode(preferences.getString(key, "auto"));
1858            mCameraDevice.setParameters(mParameters);
1859        } else if (CameraSettings.KEY_FOCUS_MODE.equals(key)) {
1860            mFocusMode = preferences.getString(key,
1861                    getString(R.string.pref_camera_focusmode_default));
1862        } else if (CameraSettings.KEY_PICTURE_SIZE.equals(key)) {
1863            String pictureSize = preferences.getString(key,
1864                    getString(R.string.pref_camera_picturesize_default));
1865            setCameraPictureSizeIfSupported(pictureSize);
1866            mCameraDevice.setParameters(mParameters);
1867        } else if (CameraSettings.KEY_JPEG_QUALITY.equals(key)) {
1868            String jpegQuality = preferences.getString(key,
1869                    getString(R.string.pref_camera_jpegquality_default));
1870            mParameters.setJpegQuality(Integer.parseInt(jpegQuality));
1871            mCameraDevice.setParameters(mParameters);
1872        } else if (CameraSettings.KEY_RECORD_LOCATION.equals(key)) {
1873            mRecordLocation = preferences.getBoolean(key, false);
1874            if (mRecordLocation) {
1875                startReceivingLocationUpdates();
1876            } else {
1877                stopReceivingLocationUpdates();
1878            }
1879        } else if (CameraSettings.KEY_COLOR_EFFECT.equals(key)) {
1880            String colorEffect = preferences.getString(key,
1881                    getString(R.string.pref_camera_coloreffect_default));
1882            mParameters.setColorEffect(colorEffect);
1883            mCameraDevice.setParameters(mParameters);
1884        } else if (CameraSettings.KEY_WHITE_BALANCE.equals(key)) {
1885            String whiteBalance = preferences.getString(key,
1886                    getString(R.string.pref_camera_whitebalance_default));
1887            mParameters.setWhiteBalance(whiteBalance);
1888            mCameraDevice.setParameters(mParameters);
1889        } else if (CameraSettings.KEY_SCENE_MODE.equals(key)) {
1890            String sceneMode = preferences.getString(key,
1891                    getString(R.string.pref_camera_scenemode_default));
1892            mParameters.setSceneMode(sceneMode);
1893            mCameraDevice.setParameters(mParameters);
1894        }
1895    }
1896
1897    @Override
1898    public void onUserInteraction() {
1899        super.onUserInteraction();
1900        keepScreenOnAwhile();
1901    }
1902
1903    private void resetScreenOn() {
1904        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1905        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1906    }
1907
1908    private void keepScreenOnAwhile() {
1909        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1910        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1911        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1912    }
1913}
1914
1915class FocusRectangle extends View {
1916
1917    @SuppressWarnings("unused")
1918    private static final String TAG = "FocusRectangle";
1919
1920    public FocusRectangle(Context context, AttributeSet attrs) {
1921        super(context, attrs);
1922    }
1923
1924    private void setDrawable(int resid) {
1925        setBackgroundDrawable(getResources().getDrawable(resid));
1926    }
1927
1928    public void showStart() {
1929        setDrawable(R.drawable.focus_focusing);
1930    }
1931
1932    public void showSuccess() {
1933        setDrawable(R.drawable.focus_focused);
1934    }
1935
1936    public void showFail() {
1937        setDrawable(R.drawable.focus_focus_failed);
1938    }
1939
1940    public void clear() {
1941        setBackgroundDrawable(null);
1942    }
1943}
1944
1945// FlashButton changes state every time it is clicked.
1946// The ModeChangeListener notifies that event.
1947class FlashButton extends ImageView implements View.OnClickListener {
1948    private static final String TAG = "FlashButton";
1949
1950    private static final int MODE_OFF = 0;
1951    private static final int MODE_ON = 1;
1952    private static final int MODE_AUTO = 2;
1953
1954    private static final String[] MODE_STRINGS = new String[] {
1955        "off", "on", "auto"
1956    };
1957
1958    private static final int[] FLASH_IMAGES = new int[] {
1959        R.drawable.flash_off,
1960        R.drawable.flash_on,
1961        R.drawable.flash_auto
1962    };
1963
1964    private int mCurrentMode;
1965    private ModeChangeListener mListener;
1966
1967    public interface ModeChangeListener {
1968        public void onFlashModeChanged(String modeString);
1969    }
1970    public FlashButton(Context context, AttributeSet attrs) {
1971        super(context, attrs);
1972        updateMode(MODE_AUTO);
1973        setOnClickListener(this);
1974    }
1975
1976    public void setMode(String modeString) {
1977        for (int i = 0; i < MODE_STRINGS.length; i++) {
1978            if (MODE_STRINGS[i].equals(modeString)) {
1979                updateModeIfNecessary(i);
1980                return;
1981            }
1982        }
1983        Log.w(TAG, "Unknown mode: " + modeString);
1984    }
1985
1986    public void setListener(ModeChangeListener listener) {
1987        mListener = listener;
1988    }
1989
1990    public void onClick(View v) {
1991        int nextMode = (mCurrentMode + 1) % FLASH_IMAGES.length;
1992        updateMode(nextMode);
1993    }
1994
1995    private void updateModeIfNecessary(int mode) {
1996        if (mode == mCurrentMode) return;
1997        if (mode < 0 || mode >= FLASH_IMAGES.length) {
1998            return;
1999        }
2000        updateMode(mode);
2001    }
2002
2003    private void updateMode(int mode) {
2004        mCurrentMode = mode;
2005        setImageResource(FLASH_IMAGES[mode]);
2006        if (mListener != null) {
2007            mListener.onFlashModeChanged(MODE_STRINGS[mode]);
2008        }
2009    }
2010}
2011