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 
17 package com.android.camera;
18 
19 import com.android.camera.ui.CameraPicker;
20 import com.android.camera.ui.FaceView;
21 import com.android.camera.ui.IndicatorControlContainer;
22 import com.android.camera.ui.PopupManager;
23 import com.android.camera.ui.Rotatable;
24 import com.android.camera.ui.RotateImageView;
25 import com.android.camera.ui.RotateLayout;
26 import com.android.camera.ui.RotateTextToast;
27 import com.android.camera.ui.SharePopup;
28 import com.android.camera.ui.ZoomControl;
29 
30 import android.app.Activity;
31 import android.content.BroadcastReceiver;
32 import android.content.ContentProviderClient;
33 import android.content.ContentResolver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences.Editor;
38 import android.content.pm.ActivityInfo;
39 import android.graphics.Bitmap;
40 import android.hardware.Camera.CameraInfo;
41 import android.hardware.Camera.Face;
42 import android.hardware.Camera.FaceDetectionListener;
43 import android.hardware.Camera.Parameters;
44 import android.hardware.Camera.PictureCallback;
45 import android.hardware.Camera.Size;
46 import android.location.Location;
47 import android.media.CameraProfile;
48 import android.media.MediaActionSound;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.Looper;
53 import android.os.Message;
54 import android.os.MessageQueue;
55 import android.os.SystemClock;
56 import android.provider.MediaStore;
57 import android.util.Log;
58 import android.view.GestureDetector;
59 import android.view.Gravity;
60 import android.view.KeyEvent;
61 import android.view.Menu;
62 import android.view.MenuItem;
63 import android.view.MenuItem.OnMenuItemClickListener;
64 import android.view.MotionEvent;
65 import android.view.OrientationEventListener;
66 import android.view.SurfaceHolder;
67 import android.view.SurfaceView;
68 import android.view.View;
69 import android.view.ViewGroup;
70 import android.view.WindowManager;
71 import android.view.animation.AnimationUtils;
72 import android.widget.ImageView;
73 import android.widget.TextView;
74 import android.widget.Toast;
75 
76 import java.io.File;
77 import java.io.FileNotFoundException;
78 import java.io.FileOutputStream;
79 import java.io.IOException;
80 import java.io.OutputStream;
81 import java.util.ArrayList;
82 import java.util.Collections;
83 import java.util.Formatter;
84 import java.util.List;
85 
86 /** The Camera activity which can preview and take pictures. */
87 public class Camera extends ActivityBase implements FocusManager.Listener,
88         View.OnTouchListener, ShutterButton.OnShutterButtonListener,
89         SurfaceHolder.Callback, ModePicker.OnModeChangeListener,
90         FaceDetectionListener, CameraPreference.OnPreferenceChangedListener,
91         LocationManager.Listener {
92 
93     private static final String TAG = "camera";
94 
95     private static final int CROP_MSG = 1;
96     private static final int FIRST_TIME_INIT = 2;
97     private static final int CLEAR_SCREEN_DELAY = 3;
98     private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
99     private static final int CHECK_DISPLAY_ROTATION = 5;
100     private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
101     private static final int UPDATE_THUMBNAIL = 7;
102 
103     // The subset of parameters we need to update in setCameraParameters().
104     private static final int UPDATE_PARAM_INITIALIZE = 1;
105     private static final int UPDATE_PARAM_ZOOM = 2;
106     private static final int UPDATE_PARAM_PREFERENCE = 4;
107     private static final int UPDATE_PARAM_ALL = -1;
108 
109     // When setCameraParametersWhenIdle() is called, we accumulate the subsets
110     // needed to be updated in mUpdateSet.
111     private int mUpdateSet;
112 
113     private static final int SCREEN_DELAY = 2 * 60 * 1000;
114 
115     private static final int ZOOM_STOPPED = 0;
116     private static final int ZOOM_START = 1;
117     private static final int ZOOM_STOPPING = 2;
118 
119     private int mZoomState = ZOOM_STOPPED;
120     private boolean mSmoothZoomSupported = false;
121     private int mZoomValue;  // The current zoom value.
122     private int mZoomMax;
123     private int mTargetZoomValue;
124     private ZoomControl mZoomControl;
125 
126     private Parameters mParameters;
127     private Parameters mInitialParams;
128     private boolean mFocusAreaSupported;
129     private boolean mMeteringAreaSupported;
130     private boolean mAeLockSupported;
131     private boolean mAwbLockSupported;
132 
133     private MyOrientationEventListener mOrientationListener;
134     // The degrees of the device rotated clockwise from its natural orientation.
135     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
136     // The orientation compensation for icons and thumbnails. Ex: if the value
137     // is 90, the UI components should be rotated 90 degrees counter-clockwise.
138     private int mOrientationCompensation = 0;
139     private ComboPreferences mPreferences;
140 
141     private static final String sTempCropFilename = "crop-temp";
142 
143     private ContentProviderClient mMediaProviderClient;
144     private SurfaceHolder mSurfaceHolder = null;
145     private ShutterButton mShutterButton;
146     private GestureDetector mPopupGestureDetector;
147     private boolean mOpenCameraFail = false;
148     private boolean mCameraDisabled = false;
149     private boolean mFaceDetectionStarted = false;
150 
151     private View mPreviewPanel;  // The container of PreviewFrameLayout.
152     private PreviewFrameLayout mPreviewFrameLayout;
153     private View mPreviewFrame;  // Preview frame area.
154     private RotateDialogController mRotateDialog;
155 
156     // A popup window that contains a bigger thumbnail and a list of apps to share.
157     private SharePopup mSharePopup;
158     // The bitmap of the last captured picture thumbnail and the URI of the
159     // original picture.
160     private Thumbnail mThumbnail;
161     // An imageview showing showing the last captured picture thumbnail.
162     private RotateImageView mThumbnailView;
163     private ModePicker mModePicker;
164     private FaceView mFaceView;
165     private RotateLayout mFocusAreaIndicator;
166     private Rotatable mReviewCancelButton;
167     private Rotatable mReviewDoneButton;
168 
169     // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
170     private String mCropValue;
171     private Uri mSaveUri;
172 
173     // Small indicators which show the camera settings in the viewfinder.
174     private TextView mExposureIndicator;
175     private ImageView mGpsIndicator;
176     private ImageView mFlashIndicator;
177     private ImageView mSceneIndicator;
178     private ImageView mWhiteBalanceIndicator;
179     private ImageView mFocusIndicator;
180     // A view group that contains all the small indicators.
181     private Rotatable mOnScreenIndicators;
182 
183     // We use a thread in ImageSaver to do the work of saving images and
184     // generating thumbnails. This reduces the shot-to-shot time.
185     private ImageSaver mImageSaver;
186 
187     private MediaActionSound mCameraSound;
188 
189     private Runnable mDoSnapRunnable = new Runnable() {
190         public void run() {
191             onShutterButtonClick();
192         }
193     };
194 
195     private final StringBuilder mBuilder = new StringBuilder();
196     private final Formatter mFormatter = new Formatter(mBuilder);
197     private final Object[] mFormatterArgs = new Object[1];
198 
199     /**
200      * An unpublished intent flag requesting to return as soon as capturing
201      * is completed.
202      *
203      * TODO: consider publishing by moving into MediaStore.
204      */
205     private static final String EXTRA_QUICK_CAPTURE =
206             "android.intent.extra.quickCapture";
207 
208     // The display rotation in degrees. This is only valid when mCameraState is
209     // not PREVIEW_STOPPED.
210     private int mDisplayRotation;
211     // The value for android.hardware.Camera.setDisplayOrientation.
212     private int mDisplayOrientation;
213     private boolean mPausing;
214     private boolean mFirstTimeInitialized;
215     private boolean mIsImageCaptureIntent;
216 
217     private static final int PREVIEW_STOPPED = 0;
218     private static final int IDLE = 1;  // preview is active
219     // Focus is in progress. The exact focus state is in Focus.java.
220     private static final int FOCUSING = 2;
221     private static final int SNAPSHOT_IN_PROGRESS = 3;
222     private int mCameraState = PREVIEW_STOPPED;
223     private boolean mSnapshotOnIdle = false;
224 
225     private ContentResolver mContentResolver;
226     private boolean mDidRegister = false;
227 
228     private LocationManager mLocationManager;
229 
230     private final ShutterCallback mShutterCallback = new ShutterCallback();
231     private final PostViewPictureCallback mPostViewPictureCallback =
232             new PostViewPictureCallback();
233     private final RawPictureCallback mRawPictureCallback =
234             new RawPictureCallback();
235     private final AutoFocusCallback mAutoFocusCallback =
236             new AutoFocusCallback();
237     private final ZoomListener mZoomListener = new ZoomListener();
238     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
239 
240     private long mFocusStartTime;
241     private long mCaptureStartTime;
242     private long mShutterCallbackTime;
243     private long mPostViewPictureCallbackTime;
244     private long mRawPictureCallbackTime;
245     private long mJpegPictureCallbackTime;
246     private long mOnResumeTime;
247     private long mPicturesRemaining;
248     private byte[] mJpegImageData;
249 
250     // These latency time are for the CameraLatency test.
251     public long mAutoFocusTime;
252     public long mShutterLag;
253     public long mShutterToPictureDisplayedTime;
254     public long mPictureDisplayedToJpegCallbackTime;
255     public long mJpegCallbackFinishTime;
256 
257     // This handles everything about focus.
258     private FocusManager mFocusManager;
259     private String mSceneMode;
260     private Toast mNotSelectableToast;
261     private Toast mNoShareToast;
262 
263     private final Handler mHandler = new MainHandler();
264     private IndicatorControlContainer mIndicatorControlContainer;
265     private PreferenceGroup mPreferenceGroup;
266 
267     // multiple cameras support
268     private int mNumberOfCameras;
269     private int mCameraId;
270     private int mFrontCameraId;
271     private int mBackCameraId;
272 
273     private boolean mQuickCapture;
274 
275     /**
276      * This Handler is used to post message back onto the main thread of the
277      * application
278      */
279     private class MainHandler extends Handler {
280         @Override
handleMessage(Message msg)281         public void handleMessage(Message msg) {
282             switch (msg.what) {
283                 case CLEAR_SCREEN_DELAY: {
284                     getWindow().clearFlags(
285                             WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
286                     break;
287                 }
288 
289                 case FIRST_TIME_INIT: {
290                     initializeFirstTime();
291                     break;
292                 }
293 
294                 case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
295                     setCameraParametersWhenIdle(0);
296                     break;
297                 }
298 
299                 case CHECK_DISPLAY_ROTATION: {
300                     // Set the display orientation if display rotation has changed.
301                     // Sometimes this happens when the device is held upside
302                     // down and camera app is opened. Rotation animation will
303                     // take some time and the rotation value we have got may be
304                     // wrong. Framework does not have a callback for this now.
305                     if (Util.getDisplayRotation(Camera.this) != mDisplayRotation) {
306                         setDisplayOrientation();
307                     }
308                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
309                         mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
310                     }
311                     break;
312                 }
313 
314                 case SHOW_TAP_TO_FOCUS_TOAST: {
315                     showTapToFocusToast();
316                     break;
317                 }
318 
319                 case UPDATE_THUMBNAIL: {
320                     mImageSaver.updateThumbnail();
321                     break;
322                 }
323             }
324         }
325     }
326 
resetExposureCompensation()327     private void resetExposureCompensation() {
328         String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
329                 CameraSettings.EXPOSURE_DEFAULT_VALUE);
330         if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
331             Editor editor = mPreferences.edit();
332             editor.putString(CameraSettings.KEY_EXPOSURE, "0");
333             editor.apply();
334             if (mIndicatorControlContainer != null) {
335                 mIndicatorControlContainer.reloadPreferences();
336             }
337         }
338     }
339 
keepMediaProviderInstance()340     private void keepMediaProviderInstance() {
341         // We want to keep a reference to MediaProvider in camera's lifecycle.
342         // TODO: Utilize mMediaProviderClient instance to replace
343         // ContentResolver calls.
344         if (mMediaProviderClient == null) {
345             mMediaProviderClient = getContentResolver()
346                     .acquireContentProviderClient(MediaStore.AUTHORITY);
347         }
348     }
349 
350     // Snapshots can only be taken after this is called. It should be called
351     // once only. We could have done these things in onCreate() but we want to
352     // make preview screen appear as soon as possible.
initializeFirstTime()353     private void initializeFirstTime() {
354         if (mFirstTimeInitialized) return;
355 
356         // Create orientation listenter. This should be done first because it
357         // takes some time to get first orientation.
358         mOrientationListener = new MyOrientationEventListener(Camera.this);
359         mOrientationListener.enable();
360 
361         // Initialize location sevice.
362         boolean recordLocation = RecordLocationPreference.get(
363                 mPreferences, getContentResolver());
364         initOnScreenIndicator();
365         mLocationManager.recordLocation(recordLocation);
366 
367         keepMediaProviderInstance();
368         checkStorage();
369 
370         // Initialize last picture button.
371         mContentResolver = getContentResolver();
372         if (!mIsImageCaptureIntent) {  // no thumbnail in image capture intent
373             initThumbnailButton();
374         }
375 
376         // Initialize shutter button.
377         mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
378         mShutterButton.setOnShutterButtonListener(this);
379         mShutterButton.setVisibility(View.VISIBLE);
380 
381         // Initialize focus UI.
382         mPreviewFrame = findViewById(R.id.camera_preview);
383         mPreviewFrame.setOnTouchListener(this);
384         mFocusAreaIndicator = (RotateLayout) findViewById(R.id.focus_indicator_rotate_layout);
385         CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
386         boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
387         mFocusManager.initialize(mFocusAreaIndicator, mPreviewFrame, mFaceView, this,
388                 mirror, mDisplayOrientation);
389         mImageSaver = new ImageSaver();
390         Util.initializeScreenBrightness(getWindow(), getContentResolver());
391         installIntentFilter();
392         initializeZoom();
393         updateOnScreenIndicators();
394         startFaceDetection();
395         // Show the tap to focus toast if this is the first start.
396         if (mFocusAreaSupported &&
397                 mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
398             // Delay the toast for one second to wait for orientation.
399             mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
400         }
401 
402         mFirstTimeInitialized = true;
403         addIdleHandler();
404     }
405 
addIdleHandler()406     private void addIdleHandler() {
407         MessageQueue queue = Looper.myQueue();
408         queue.addIdleHandler(new MessageQueue.IdleHandler() {
409             public boolean queueIdle() {
410                 Storage.ensureOSXCompatible();
411                 return false;
412             }
413         });
414     }
415 
initThumbnailButton()416     private void initThumbnailButton() {
417         // Load the thumbnail from the disk.
418         mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
419         updateThumbnailButton();
420     }
421 
updateThumbnailButton()422     private void updateThumbnailButton() {
423         // Update last image if URI is invalid and the storage is ready.
424         if ((mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver))
425                 && mPicturesRemaining >= 0) {
426             mThumbnail = Thumbnail.getLastThumbnail(mContentResolver);
427         }
428         if (mThumbnail != null) {
429             mThumbnailView.setBitmap(mThumbnail.getBitmap());
430         } else {
431             mThumbnailView.setBitmap(null);
432         }
433     }
434 
435     // If the activity is paused and resumed, this method will be called in
436     // onResume.
initializeSecondTime()437     private void initializeSecondTime() {
438         // Start orientation listener as soon as possible because it takes
439         // some time to get first orientation.
440         mOrientationListener.enable();
441 
442         // Start location update if needed.
443         boolean recordLocation = RecordLocationPreference.get(
444                 mPreferences, getContentResolver());
445         mLocationManager.recordLocation(recordLocation);
446 
447         installIntentFilter();
448         mImageSaver = new ImageSaver();
449         initializeZoom();
450         keepMediaProviderInstance();
451         checkStorage();
452         hidePostCaptureAlert();
453 
454         if (!mIsImageCaptureIntent) {
455             updateThumbnailButton();
456             mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
457         }
458     }
459 
460     private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
461         // only for immediate zoom
462         @Override
onZoomValueChanged(int index)463         public void onZoomValueChanged(int index) {
464             Camera.this.onZoomValueChanged(index);
465         }
466 
467         // only for smooth zoom
468         @Override
onZoomStateChanged(int state)469         public void onZoomStateChanged(int state) {
470             if (mPausing) return;
471 
472             Log.v(TAG, "zoom picker state=" + state);
473             if (state == ZoomControl.ZOOM_IN) {
474                 Camera.this.onZoomValueChanged(mZoomMax);
475             } else if (state == ZoomControl.ZOOM_OUT) {
476                 Camera.this.onZoomValueChanged(0);
477             } else {
478                 mTargetZoomValue = -1;
479                 if (mZoomState == ZOOM_START) {
480                     mZoomState = ZOOM_STOPPING;
481                     mCameraDevice.stopSmoothZoom();
482                 }
483             }
484         }
485     }
486 
initializeZoom()487     private void initializeZoom() {
488         // Get the parameter to make sure we have the up-to-date zoom value.
489         mParameters = mCameraDevice.getParameters();
490         if (!mParameters.isZoomSupported()) return;
491         mZoomMax = mParameters.getMaxZoom();
492         // Currently we use immediate zoom for fast zooming to get better UX and
493         // there is no plan to take advantage of the smooth zoom.
494         mZoomControl.setZoomMax(mZoomMax);
495         mZoomControl.setZoomIndex(mParameters.getZoom());
496         mZoomControl.setSmoothZoomSupported(mSmoothZoomSupported);
497         mZoomControl.setOnZoomChangeListener(new ZoomChangeListener());
498         mCameraDevice.setZoomChangeListener(mZoomListener);
499     }
500 
onZoomValueChanged(int index)501     private void onZoomValueChanged(int index) {
502         // Not useful to change zoom value when the activity is paused.
503         if (mPausing) return;
504 
505         if (mSmoothZoomSupported) {
506             if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
507                 mTargetZoomValue = index;
508                 if (mZoomState == ZOOM_START) {
509                     mZoomState = ZOOM_STOPPING;
510                     mCameraDevice.stopSmoothZoom();
511                 }
512             } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
513                 mTargetZoomValue = index;
514                 mCameraDevice.startSmoothZoom(index);
515                 mZoomState = ZOOM_START;
516             }
517         } else {
518             mZoomValue = index;
519             setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
520         }
521     }
522 
523     @Override
startFaceDetection()524     public void startFaceDetection() {
525         if (mFaceDetectionStarted || mCameraState != IDLE) return;
526         if (mParameters.getMaxNumDetectedFaces() > 0) {
527             mFaceDetectionStarted = true;
528             mFaceView = (FaceView) findViewById(R.id.face_view);
529             mFaceView.clear();
530             mFaceView.setVisibility(View.VISIBLE);
531             mFaceView.setDisplayOrientation(mDisplayOrientation);
532             CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
533             mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
534             mFaceView.resume();
535             mCameraDevice.setFaceDetectionListener(this);
536             mCameraDevice.startFaceDetection();
537         }
538     }
539 
540     @Override
stopFaceDetection()541     public void stopFaceDetection() {
542         if (!mFaceDetectionStarted) return;
543         if (mParameters.getMaxNumDetectedFaces() > 0) {
544             mFaceDetectionStarted = false;
545             mCameraDevice.setFaceDetectionListener(null);
546             mCameraDevice.stopFaceDetection();
547             if (mFaceView != null) mFaceView.clear();
548         }
549     }
550 
551     private class PopupGestureListener
552             extends GestureDetector.SimpleOnGestureListener {
553         @Override
onDown(MotionEvent e)554         public boolean onDown(MotionEvent e) {
555             // Check if the popup window is visible.
556             View popup = mIndicatorControlContainer.getActiveSettingPopup();
557             if (popup == null) return false;
558 
559 
560             // Let popup window, indicator control or preview frame handle the
561             // event by themselves. Dismiss the popup window if users touch on
562             // other areas.
563             if (!Util.pointInView(e.getX(), e.getY(), popup)
564                     && !Util.pointInView(e.getX(), e.getY(), mIndicatorControlContainer)
565                     && !Util.pointInView(e.getX(), e.getY(), mPreviewFrame)) {
566                 mIndicatorControlContainer.dismissSettingPopup();
567                 // Let event fall through.
568             }
569             return false;
570         }
571     }
572 
573     @Override
dispatchTouchEvent(MotionEvent m)574     public boolean dispatchTouchEvent(MotionEvent m) {
575         // Check if the popup window should be dismissed first.
576         if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
577             return true;
578         }
579 
580         return super.dispatchTouchEvent(m);
581     }
582 
583     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
584         @Override
585         public void onReceive(Context context, Intent intent) {
586             String action = intent.getAction();
587             Log.d(TAG, "Received intent action=" + action);
588             if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
589                     || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
590                     || action.equals(Intent.ACTION_MEDIA_CHECKING)) {
591                 checkStorage();
592             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
593                 checkStorage();
594                 if (!mIsImageCaptureIntent) {
595                     updateThumbnailButton();
596                 }
597             }
598         }
599     };
600 
initOnScreenIndicator()601     private void initOnScreenIndicator() {
602         mGpsIndicator = (ImageView) findViewById(R.id.onscreen_gps_indicator);
603         mExposureIndicator = (TextView) findViewById(R.id.onscreen_exposure_indicator);
604         mFlashIndicator = (ImageView) findViewById(R.id.onscreen_flash_indicator);
605         mSceneIndicator = (ImageView) findViewById(R.id.onscreen_scene_indicator);
606         mWhiteBalanceIndicator =
607                 (ImageView) findViewById(R.id.onscreen_white_balance_indicator);
608         mFocusIndicator = (ImageView) findViewById(R.id.onscreen_focus_indicator);
609     }
610 
611     @Override
showGpsOnScreenIndicator(boolean hasSignal)612     public void showGpsOnScreenIndicator(boolean hasSignal) {
613         if (mGpsIndicator == null) {
614             return;
615         }
616         if (hasSignal) {
617             mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_on);
618         } else {
619             mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_no_signal);
620         }
621         mGpsIndicator.setVisibility(View.VISIBLE);
622     }
623 
624     @Override
hideGpsOnScreenIndicator()625     public void hideGpsOnScreenIndicator() {
626         if (mGpsIndicator == null) {
627             return;
628         }
629         mGpsIndicator.setVisibility(View.GONE);
630     }
631 
updateExposureOnScreenIndicator(int value)632     private void updateExposureOnScreenIndicator(int value) {
633         if (mExposureIndicator == null) {
634             return;
635         }
636         if (value == 0) {
637             mExposureIndicator.setText("");
638             mExposureIndicator.setVisibility(View.GONE);
639         } else {
640             float step = mParameters.getExposureCompensationStep();
641             mFormatterArgs[0] = value * step;
642             mBuilder.delete(0, mBuilder.length());
643             mFormatter.format("%+1.1f", mFormatterArgs);
644             String exposure = mFormatter.toString();
645             mExposureIndicator.setText(exposure);
646             mExposureIndicator.setVisibility(View.VISIBLE);
647         }
648     }
649 
updateFlashOnScreenIndicator(String value)650     private void updateFlashOnScreenIndicator(String value) {
651         if (mFlashIndicator == null) {
652             return;
653         }
654         if (Parameters.FLASH_MODE_AUTO.equals(value)) {
655             mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_auto);
656             mFlashIndicator.setVisibility(View.VISIBLE);
657         } else if (Parameters.FLASH_MODE_ON.equals(value)) {
658             mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_on);
659             mFlashIndicator.setVisibility(View.VISIBLE);
660         } else if (Parameters.FLASH_MODE_OFF.equals(value)) {
661             mFlashIndicator.setVisibility(View.GONE);
662         }
663     }
664 
updateSceneOnScreenIndicator(boolean isVisible)665     private void updateSceneOnScreenIndicator(boolean isVisible) {
666         if (mSceneIndicator == null) {
667             return;
668         }
669         mSceneIndicator.setVisibility(isVisible ? View.VISIBLE : View.GONE);
670     }
671 
updateWhiteBalanceOnScreenIndicator(String value)672     private void updateWhiteBalanceOnScreenIndicator(String value) {
673         if (mWhiteBalanceIndicator == null) {
674             return;
675         }
676         if (Parameters.WHITE_BALANCE_AUTO.equals(value)) {
677             mWhiteBalanceIndicator.setVisibility(View.GONE);
678         } else {
679             if (Parameters.WHITE_BALANCE_FLUORESCENT.equals(value)) {
680                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_fluorescent);
681             } else if (Parameters.WHITE_BALANCE_INCANDESCENT.equals(value)) {
682                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_incandescent);
683             } else if (Parameters.WHITE_BALANCE_DAYLIGHT.equals(value)) {
684                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_sunlight);
685             } else if (Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT.equals(value)) {
686                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_cloudy);
687             }
688             mWhiteBalanceIndicator.setVisibility(View.VISIBLE);
689         }
690     }
691 
updateFocusOnScreenIndicator(String value)692     private void updateFocusOnScreenIndicator(String value) {
693         if (mFocusIndicator == null) {
694             return;
695         }
696         if (Parameters.FOCUS_MODE_INFINITY.equals(value)) {
697             mFocusIndicator.setImageResource(R.drawable.ic_indicators_landscape);
698             mFocusIndicator.setVisibility(View.VISIBLE);
699         } else if (Parameters.FOCUS_MODE_MACRO.equals(value)) {
700             mFocusIndicator.setImageResource(R.drawable.ic_indicators_macro);
701             mFocusIndicator.setVisibility(View.VISIBLE);
702         } else {
703             mFocusIndicator.setVisibility(View.GONE);
704         }
705     }
706 
updateOnScreenIndicators()707     private void updateOnScreenIndicators() {
708         boolean isAutoScene = !(Parameters.SCENE_MODE_AUTO.equals(mParameters.getSceneMode()));
709         updateSceneOnScreenIndicator(isAutoScene);
710         updateExposureOnScreenIndicator(CameraSettings.readExposure(mPreferences));
711         updateFlashOnScreenIndicator(mParameters.getFlashMode());
712         updateWhiteBalanceOnScreenIndicator(mParameters.getWhiteBalance());
713         updateFocusOnScreenIndicator(mParameters.getFocusMode());
714     }
715     private final class ShutterCallback
716             implements android.hardware.Camera.ShutterCallback {
onShutter()717         public void onShutter() {
718             mShutterCallbackTime = System.currentTimeMillis();
719             mShutterLag = mShutterCallbackTime - mCaptureStartTime;
720             Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
721             mFocusManager.onShutter();
722         }
723     }
724 
725     private final class PostViewPictureCallback implements PictureCallback {
onPictureTaken( byte [] data, android.hardware.Camera camera)726         public void onPictureTaken(
727                 byte [] data, android.hardware.Camera camera) {
728             mPostViewPictureCallbackTime = System.currentTimeMillis();
729             Log.v(TAG, "mShutterToPostViewCallbackTime = "
730                     + (mPostViewPictureCallbackTime - mShutterCallbackTime)
731                     + "ms");
732         }
733     }
734 
735     private final class RawPictureCallback implements PictureCallback {
onPictureTaken( byte [] rawData, android.hardware.Camera camera)736         public void onPictureTaken(
737                 byte [] rawData, android.hardware.Camera camera) {
738             mRawPictureCallbackTime = System.currentTimeMillis();
739             Log.v(TAG, "mShutterToRawCallbackTime = "
740                     + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
741         }
742     }
743 
744     private final class JpegPictureCallback implements PictureCallback {
745         Location mLocation;
746 
JpegPictureCallback(Location loc)747         public JpegPictureCallback(Location loc) {
748             mLocation = loc;
749         }
750 
onPictureTaken( final byte [] jpegData, final android.hardware.Camera camera)751         public void onPictureTaken(
752                 final byte [] jpegData, final android.hardware.Camera camera) {
753             if (mPausing) {
754                 return;
755             }
756 
757             mJpegPictureCallbackTime = System.currentTimeMillis();
758             // If postview callback has arrived, the captured image is displayed
759             // in postview callback. If not, the captured image is displayed in
760             // raw picture callback.
761             if (mPostViewPictureCallbackTime != 0) {
762                 mShutterToPictureDisplayedTime =
763                         mPostViewPictureCallbackTime - mShutterCallbackTime;
764                 mPictureDisplayedToJpegCallbackTime =
765                         mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
766             } else {
767                 mShutterToPictureDisplayedTime =
768                         mRawPictureCallbackTime - mShutterCallbackTime;
769                 mPictureDisplayedToJpegCallbackTime =
770                         mJpegPictureCallbackTime - mRawPictureCallbackTime;
771             }
772             Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
773                     + mPictureDisplayedToJpegCallbackTime + "ms");
774 
775             if (!mIsImageCaptureIntent) {
776                 startPreview();
777                 startFaceDetection();
778             }
779 
780             if (!mIsImageCaptureIntent) {
781                 Size s = mParameters.getPictureSize();
782                 mImageSaver.addImage(jpegData, mLocation, s.width, s.height);
783             } else {
784                 mJpegImageData = jpegData;
785                 if (!mQuickCapture) {
786                     showPostCaptureAlert();
787                 } else {
788                     doAttach();
789                 }
790             }
791 
792             // Check this in advance of each shot so we don't add to shutter
793             // latency. It's true that someone else could write to the SD card in
794             // the mean time and fill it, but that could have happened between the
795             // shutter press and saving the JPEG too.
796             checkStorage();
797 
798             long now = System.currentTimeMillis();
799             mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
800             Log.v(TAG, "mJpegCallbackFinishTime = "
801                     + mJpegCallbackFinishTime + "ms");
802             mJpegPictureCallbackTime = 0;
803         }
804     }
805 
806     private final class AutoFocusCallback
807             implements android.hardware.Camera.AutoFocusCallback {
onAutoFocus( boolean focused, android.hardware.Camera camera)808         public void onAutoFocus(
809                 boolean focused, android.hardware.Camera camera) {
810             if (mPausing) return;
811 
812             mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
813             Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
814             setCameraState(IDLE);
815             mFocusManager.onAutoFocus(focused);
816         }
817     }
818 
819     private final class ZoomListener
820             implements android.hardware.Camera.OnZoomChangeListener {
821         @Override
onZoomChange( int value, boolean stopped, android.hardware.Camera camera)822         public void onZoomChange(
823                 int value, boolean stopped, android.hardware.Camera camera) {
824             Log.v(TAG, "Zoom changed: value=" + value + ". stopped=" + stopped);
825             mZoomValue = value;
826 
827             // Update the UI when we get zoom value.
828             mZoomControl.setZoomIndex(value);
829 
830             // Keep mParameters up to date. We do not getParameter again in
831             // takePicture. If we do not do this, wrong zoom value will be set.
832             mParameters.setZoom(value);
833 
834             if (stopped && mZoomState != ZOOM_STOPPED) {
835                 if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
836                     mCameraDevice.startSmoothZoom(mTargetZoomValue);
837                     mZoomState = ZOOM_START;
838                 } else {
839                     mZoomState = ZOOM_STOPPED;
840                 }
841             }
842         }
843     }
844 
845     // Each SaveRequest remembers the data needed to save an image.
846     private static class SaveRequest {
847         byte[] data;
848         Location loc;
849         int width, height;
850         long dateTaken;
851         int previewWidth;
852     }
853 
854     // We use a queue to store the SaveRequests that have not been completed
855     // yet. The main thread puts the request into the queue. The saver thread
856     // gets it from the queue, does the work, and removes it from the queue.
857     //
858     // There are several cases the main thread needs to wait for the saver
859     // thread to finish all the work in the queue:
860     // (1) When the activity's onPause() is called, we need to finish all the
861     // work, so other programs (like Gallery) can see all the images.
862     // (2) When we need to show the SharePop, we need to finish all the work
863     // too, because we want to show the thumbnail of the last image taken.
864     //
865     // If the queue becomes too long, adding a new request will block the main
866     // thread until the queue length drops below the threshold (QUEUE_LIMIT).
867     // If we don't do this, we may face several problems: (1) We may OOM
868     // because we are holding all the jpeg data in memory. (2) We may ANR
869     // when we need to wait for saver thread finishing all the work (in
870     // onPause() or showSharePopup()) because the time to finishing a long queue
871     // of work may be too long.
872     private class ImageSaver extends Thread {
873         private static final int QUEUE_LIMIT = 3;
874 
875         private ArrayList<SaveRequest> mQueue;
876         private Thumbnail mPendingThumbnail;
877         private Object mUpdateThumbnailLock = new Object();
878         private boolean mStop;
879 
880         // Runs in main thread
ImageSaver()881         public ImageSaver() {
882             mQueue = new ArrayList<SaveRequest>();
883             start();
884         }
885 
886         // Runs in main thread
addImage(final byte[] data, Location loc, int width, int height)887         public void addImage(final byte[] data, Location loc, int width,
888                 int height) {
889             SaveRequest r = new SaveRequest();
890             r.data = data;
891             r.loc = (loc == null) ? null : new Location(loc);  // make a copy
892             r.width = width;
893             r.height = height;
894             r.dateTaken = System.currentTimeMillis();
895             if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
896                 r.previewWidth = mPreviewFrameLayout.getHeight();
897             } else {
898                 r.previewWidth = mPreviewFrameLayout.getWidth();
899             }
900             synchronized (this) {
901                 while (mQueue.size() >= QUEUE_LIMIT) {
902                     try {
903                         wait();
904                     } catch (InterruptedException ex) {
905                         // ignore.
906                     }
907                 }
908                 mQueue.add(r);
909                 notifyAll();  // Tell saver thread there is new work to do.
910             }
911         }
912 
913         // Runs in saver thread
914         @Override
run()915         public void run() {
916             while (true) {
917                 SaveRequest r;
918                 synchronized (this) {
919                     if (mQueue.isEmpty()) {
920                         notifyAll();  // notify main thread in waitDone
921 
922                         // Note that we can only stop after we saved all images
923                         // in the queue.
924                         if (mStop) break;
925 
926                         try {
927                             wait();
928                         } catch (InterruptedException ex) {
929                             // ignore.
930                         }
931                         continue;
932                     }
933                     r = mQueue.get(0);
934                 }
935                 storeImage(r.data, r.loc, r.width, r.height, r.dateTaken,
936                         r.previewWidth);
937                 synchronized(this) {
938                     mQueue.remove(0);
939                     notifyAll();  // the main thread may wait in addImage
940                 }
941             }
942         }
943 
944         // Runs in main thread
waitDone()945         public void waitDone() {
946             synchronized (this) {
947                 while (!mQueue.isEmpty()) {
948                     try {
949                         wait();
950                     } catch (InterruptedException ex) {
951                         // ignore.
952                     }
953                 }
954             }
955             updateThumbnail();
956         }
957 
958         // Runs in main thread
finish()959         public void finish() {
960             waitDone();
961             synchronized (this) {
962                 mStop = true;
963                 notifyAll();
964             }
965             try {
966                 join();
967             } catch (InterruptedException ex) {
968                 // ignore.
969             }
970         }
971 
972         // Runs in main thread (because we need to update mThumbnailView in the
973         // main thread)
updateThumbnail()974         public void updateThumbnail() {
975             Thumbnail t;
976             synchronized (mUpdateThumbnailLock) {
977                 mHandler.removeMessages(UPDATE_THUMBNAIL);
978                 t = mPendingThumbnail;
979                 mPendingThumbnail = null;
980             }
981 
982             if (t != null) {
983                 mThumbnail = t;
984                 mThumbnailView.setBitmap(mThumbnail.getBitmap());
985             }
986             // Share popup may still have the reference to the old thumbnail. Clear it.
987             mSharePopup = null;
988         }
989 
990         // Runs in saver thread
storeImage(final byte[] data, Location loc, int width, int height, long dateTaken, int previewWidth)991         private void storeImage(final byte[] data, Location loc, int width,
992                 int height, long dateTaken, int previewWidth) {
993             String title = Util.createJpegName(dateTaken);
994             int orientation = Exif.getOrientation(data);
995             Uri uri = Storage.addImage(mContentResolver, title, dateTaken,
996                     loc, orientation, data, width, height);
997             if (uri != null) {
998                 boolean needThumbnail;
999                 synchronized (this) {
1000                     // If the number of requests in the queue (include the
1001                     // current one) is greater than 1, we don't need to generate
1002                     // thumbnail for this image. Because we'll soon replace it
1003                     // with the thumbnail for some image later in the queue.
1004                     needThumbnail = (mQueue.size() <= 1);
1005                 }
1006                 if (needThumbnail) {
1007                     // Create a thumbnail whose width is equal or bigger than
1008                     // that of the preview.
1009                     int ratio = (int) Math.ceil((double) width / previewWidth);
1010                     int inSampleSize = Integer.highestOneBit(ratio);
1011                     Thumbnail t = Thumbnail.createThumbnail(
1012                                 data, orientation, inSampleSize, uri);
1013                     synchronized (mUpdateThumbnailLock) {
1014                         // We need to update the thumbnail in the main thread,
1015                         // so send a message to run updateThumbnail().
1016                         mPendingThumbnail = t;
1017                         mHandler.sendEmptyMessage(UPDATE_THUMBNAIL);
1018                     }
1019                 }
1020                 Util.broadcastNewPicture(Camera.this, uri);
1021             }
1022         }
1023     }
1024 
setCameraState(int state)1025     private void setCameraState(int state) {
1026         mCameraState = state;
1027         switch (state) {
1028             case SNAPSHOT_IN_PROGRESS:
1029             case FOCUSING:
1030                 enableCameraControls(false);
1031                 break;
1032             case IDLE:
1033             case PREVIEW_STOPPED:
1034                 enableCameraControls(true);
1035                 break;
1036         }
1037     }
1038 
1039     @Override
capture()1040     public boolean capture() {
1041         // If we are already in the middle of taking a snapshot then ignore.
1042         if (mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) {
1043             return false;
1044         }
1045         mCaptureStartTime = System.currentTimeMillis();
1046         mPostViewPictureCallbackTime = 0;
1047         mJpegImageData = null;
1048 
1049         // Set rotation and gps data.
1050         Util.setRotationParameter(mParameters, mCameraId, mOrientation);
1051         Location loc = mLocationManager.getCurrentLocation();
1052         Util.setGpsParameters(mParameters, loc);
1053         mCameraDevice.setParameters(mParameters);
1054 
1055         mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
1056                 mPostViewPictureCallback, new JpegPictureCallback(loc));
1057         mFaceDetectionStarted = false;
1058         setCameraState(SNAPSHOT_IN_PROGRESS);
1059         return true;
1060     }
1061 
1062     @Override
setFocusParameters()1063     public void setFocusParameters() {
1064         setCameraParameters(UPDATE_PARAM_PREFERENCE);
1065     }
1066 
1067     @Override
playSound(int soundId)1068     public void playSound(int soundId) {
1069         mCameraSound.play(soundId);
1070     }
1071 
saveDataToFile(String filePath, byte[] data)1072     private boolean saveDataToFile(String filePath, byte[] data) {
1073         FileOutputStream f = null;
1074         try {
1075             f = new FileOutputStream(filePath);
1076             f.write(data);
1077         } catch (IOException e) {
1078             return false;
1079         } finally {
1080             Util.closeSilently(f);
1081         }
1082         return true;
1083     }
1084 
getPreferredCameraId()1085     private void getPreferredCameraId() {
1086         mPreferences = new ComboPreferences(this);
1087         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
1088 
1089         mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
1090         int attemptedCameraId = CameraSettings.readPreferredCameraId(mPreferences);
1091 
1092         // It is possible that the user can connect/disconnect cameras
1093         // between device boots.
1094         // We need to check that the preferred camera ID
1095         // does not refer to a disconnected camera.
1096         if (attemptedCameraId >= mNumberOfCameras) {
1097             Log.v(TAG, "Preferred camera (id= " + attemptedCameraId +
1098                        ") missing. Defaulting to the first one");
1099             mCameraId = 0;
1100         } else {
1101             mCameraId = attemptedCameraId;
1102         }
1103 
1104         // Testing purpose. Launch a specific camera through the intent extras.
1105         int intentCameraId = Util.getCameraFacingIntentExtras(this);
1106         if (intentCameraId != -1) {
1107             mCameraId = intentCameraId;
1108         }
1109     }
1110 
1111     Thread mCameraOpenThread = new Thread(new Runnable() {
1112         public void run() {
1113             try {
1114                 mCameraDevice = Util.openCamera(Camera.this, mCameraId);
1115             } catch (CameraHardwareException e) {
1116                 mOpenCameraFail = true;
1117             } catch (CameraDisabledException e) {
1118                 mCameraDisabled = true;
1119             }
1120         }
1121     });
1122 
1123     Thread mCameraPreviewThread = new Thread(new Runnable() {
1124         public void run() {
1125             initializeCapabilities();
1126             startPreview();
1127         }
1128     });
1129 
1130     @Override
onCreate(Bundle icicle)1131     public void onCreate(Bundle icicle) {
1132         super.onCreate(icicle);
1133         getPreferredCameraId();
1134         String[] defaultFocusModes = getResources().getStringArray(
1135                 R.array.pref_camera_focusmode_default_array);
1136         mFocusManager = new FocusManager(mPreferences, defaultFocusModes);
1137 
1138         /*
1139          * To reduce startup time, we start the camera open and preview threads.
1140          * We make sure the preview is started at the end of onCreate.
1141          */
1142         mCameraOpenThread.start();
1143 
1144         mIsImageCaptureIntent = isImageCaptureIntent();
1145         setContentView(R.layout.camera);
1146         if (mIsImageCaptureIntent) {
1147             mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
1148             mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
1149             findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
1150         } else {
1151             mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
1152             mThumbnailView.enableFilter(false);
1153             mThumbnailView.setVisibility(View.VISIBLE);
1154         }
1155 
1156         mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
1157 
1158         mPreferences.setLocalId(this, mCameraId);
1159         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1160 
1161         mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
1162 
1163         // we need to reset exposure for the preview
1164         resetExposureCompensation();
1165 
1166         Util.enterLightsOutMode(getWindow());
1167 
1168         // don't set mSurfaceHolder here. We have it set ONLY within
1169         // surfaceChanged / surfaceDestroyed, other parts of the code
1170         // assume that when it is set, the surface is also set.
1171         SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
1172         SurfaceHolder holder = preview.getHolder();
1173         holder.addCallback(this);
1174         holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
1175 
1176         // Make sure camera device is opened.
1177         try {
1178             mCameraOpenThread.join();
1179             mCameraOpenThread = null;
1180             if (mOpenCameraFail) {
1181                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1182                 return;
1183             } else if (mCameraDisabled) {
1184                 Util.showErrorAndFinish(this, R.string.camera_disabled);
1185                 return;
1186             }
1187         } catch (InterruptedException ex) {
1188             // ignore
1189         }
1190         mCameraPreviewThread.start();
1191 
1192         if (mIsImageCaptureIntent) {
1193             setupCaptureParams();
1194         } else {
1195             mModePicker = (ModePicker) findViewById(R.id.mode_picker);
1196             mModePicker.setVisibility(View.VISIBLE);
1197             mModePicker.setOnModeChangeListener(this);
1198             mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
1199         }
1200 
1201         mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
1202         mOnScreenIndicators = (Rotatable) findViewById(R.id.on_screen_indicators);
1203         mLocationManager = new LocationManager(this, this);
1204 
1205         mBackCameraId = CameraHolder.instance().getBackCameraId();
1206         mFrontCameraId = CameraHolder.instance().getFrontCameraId();
1207 
1208         // Wait until the camera settings are retrieved.
1209         synchronized (mCameraPreviewThread) {
1210             try {
1211                 mCameraPreviewThread.wait();
1212             } catch (InterruptedException ex) {
1213                 // ignore
1214             }
1215         }
1216 
1217         // Do this after starting preview because it depends on camera
1218         // parameters.
1219         initializeIndicatorControl();
1220         mCameraSound = new MediaActionSound();
1221 
1222         // Make sure preview is started.
1223         try {
1224             mCameraPreviewThread.join();
1225         } catch (InterruptedException ex) {
1226             // ignore
1227         }
1228         mCameraPreviewThread = null;
1229     }
1230 
overrideCameraSettings(final String flashMode, final String whiteBalance, final String focusMode)1231     private void overrideCameraSettings(final String flashMode,
1232             final String whiteBalance, final String focusMode) {
1233         if (mIndicatorControlContainer != null) {
1234             mIndicatorControlContainer.overrideSettings(
1235                     CameraSettings.KEY_FLASH_MODE, flashMode,
1236                     CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1237                     CameraSettings.KEY_FOCUS_MODE, focusMode);
1238         }
1239     }
1240 
updateSceneModeUI()1241     private void updateSceneModeUI() {
1242         // If scene mode is set, we cannot set flash mode, white balance, and
1243         // focus mode, instead, we read it from driver
1244         if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1245             overrideCameraSettings(mParameters.getFlashMode(),
1246                     mParameters.getWhiteBalance(), mParameters.getFocusMode());
1247         } else {
1248             overrideCameraSettings(null, null, null);
1249         }
1250     }
1251 
loadCameraPreferences()1252     private void loadCameraPreferences() {
1253         CameraSettings settings = new CameraSettings(this, mInitialParams,
1254                 mCameraId, CameraHolder.instance().getCameraInfo());
1255         mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1256     }
1257 
initializeIndicatorControl()1258     private void initializeIndicatorControl() {
1259         // setting the indicator buttons.
1260         mIndicatorControlContainer =
1261                 (IndicatorControlContainer) findViewById(R.id.indicator_control);
1262         if (mIndicatorControlContainer == null) return;
1263         loadCameraPreferences();
1264         final String[] SETTING_KEYS = {
1265                 CameraSettings.KEY_FLASH_MODE,
1266                 CameraSettings.KEY_WHITE_BALANCE,
1267                 CameraSettings.KEY_EXPOSURE,
1268                 CameraSettings.KEY_SCENE_MODE};
1269         final String[] OTHER_SETTING_KEYS = {
1270                 CameraSettings.KEY_RECORD_LOCATION,
1271                 CameraSettings.KEY_PICTURE_SIZE,
1272                 CameraSettings.KEY_FOCUS_MODE};
1273 
1274         CameraPicker.setImageResourceId(R.drawable.ic_switch_photo_facing_holo_light);
1275         mIndicatorControlContainer.initialize(this, mPreferenceGroup,
1276                 mParameters.isZoomSupported(),
1277                 SETTING_KEYS, OTHER_SETTING_KEYS);
1278         updateSceneModeUI();
1279         mIndicatorControlContainer.setListener(this);
1280     }
1281 
collapseCameraControls()1282     private boolean collapseCameraControls() {
1283         if ((mIndicatorControlContainer != null)
1284                 && mIndicatorControlContainer.dismissSettingPopup()) {
1285             return true;
1286         }
1287         return false;
1288     }
1289 
enableCameraControls(boolean enable)1290     private void enableCameraControls(boolean enable) {
1291         if (mIndicatorControlContainer != null) {
1292             mIndicatorControlContainer.setEnabled(enable);
1293         }
1294         if (mModePicker != null) mModePicker.setEnabled(enable);
1295         if (mZoomControl != null) mZoomControl.setEnabled(enable);
1296         if (mThumbnailView != null) mThumbnailView.setEnabled(enable);
1297     }
1298 
1299     private class MyOrientationEventListener
1300             extends OrientationEventListener {
MyOrientationEventListener(Context context)1301         public MyOrientationEventListener(Context context) {
1302             super(context);
1303         }
1304 
1305         @Override
onOrientationChanged(int orientation)1306         public void onOrientationChanged(int orientation) {
1307             // We keep the last known orientation. So if the user first orient
1308             // the camera then point the camera to floor or sky, we still have
1309             // the correct orientation.
1310             if (orientation == ORIENTATION_UNKNOWN) return;
1311             mOrientation = Util.roundOrientation(orientation, mOrientation);
1312             // When the screen is unlocked, display rotation may change. Always
1313             // calculate the up-to-date orientationCompensation.
1314             int orientationCompensation = mOrientation
1315                     + Util.getDisplayRotation(Camera.this);
1316             if (mOrientationCompensation != orientationCompensation) {
1317                 mOrientationCompensation = orientationCompensation;
1318                 setOrientationIndicator(mOrientationCompensation);
1319             }
1320 
1321             // Show the toast after getting the first orientation changed.
1322             if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
1323                 mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
1324                 showTapToFocusToast();
1325             }
1326         }
1327     }
1328 
setOrientationIndicator(int orientation)1329     private void setOrientationIndicator(int orientation) {
1330         Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup,
1331                 mIndicatorControlContainer, mZoomControl, mFocusAreaIndicator, mFaceView,
1332                 mReviewCancelButton, mReviewDoneButton, mRotateDialog, mOnScreenIndicators};
1333         for (Rotatable indicator : indicators) {
1334             if (indicator != null) indicator.setOrientation(orientation);
1335         }
1336     }
1337 
1338     @Override
onStop()1339     public void onStop() {
1340         super.onStop();
1341         if (mMediaProviderClient != null) {
1342             mMediaProviderClient.release();
1343             mMediaProviderClient = null;
1344         }
1345     }
1346 
checkStorage()1347     private void checkStorage() {
1348         mPicturesRemaining = Storage.getAvailableSpace();
1349         if (mPicturesRemaining > Storage.LOW_STORAGE_THRESHOLD) {
1350             mPicturesRemaining = (mPicturesRemaining - Storage.LOW_STORAGE_THRESHOLD)
1351                     / Storage.PICTURE_SIZE;
1352         } else if (mPicturesRemaining > 0) {
1353             mPicturesRemaining = 0;
1354         }
1355 
1356         updateStorageHint();
1357     }
1358 
1359     @OnClickAttr
onThumbnailClicked(View v)1360     public void onThumbnailClicked(View v) {
1361         if (isCameraIdle() && mThumbnail != null) {
1362             showSharePopup();
1363         }
1364     }
1365 
1366     @OnClickAttr
onReviewRetakeClicked(View v)1367     public void onReviewRetakeClicked(View v) {
1368         hidePostCaptureAlert();
1369         startPreview();
1370         startFaceDetection();
1371     }
1372 
1373     @OnClickAttr
onReviewDoneClicked(View v)1374     public void onReviewDoneClicked(View v) {
1375         doAttach();
1376     }
1377 
1378     @OnClickAttr
onReviewCancelClicked(View v)1379     public void onReviewCancelClicked(View v) {
1380         doCancel();
1381     }
1382 
doAttach()1383     private void doAttach() {
1384         if (mPausing) {
1385             return;
1386         }
1387 
1388         byte[] data = mJpegImageData;
1389 
1390         if (mCropValue == null) {
1391             // First handle the no crop case -- just return the value.  If the
1392             // caller specifies a "save uri" then write the data to it's
1393             // stream. Otherwise, pass back a scaled down version of the bitmap
1394             // directly in the extras.
1395             if (mSaveUri != null) {
1396                 OutputStream outputStream = null;
1397                 try {
1398                     outputStream = mContentResolver.openOutputStream(mSaveUri);
1399                     outputStream.write(data);
1400                     outputStream.close();
1401 
1402                     setResultEx(RESULT_OK);
1403                     finish();
1404                 } catch (IOException ex) {
1405                     // ignore exception
1406                 } finally {
1407                     Util.closeSilently(outputStream);
1408                 }
1409             } else {
1410                 int orientation = Exif.getOrientation(data);
1411                 Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1412                 bitmap = Util.rotate(bitmap, orientation);
1413                 setResultEx(RESULT_OK,
1414                         new Intent("inline-data").putExtra("data", bitmap));
1415                 finish();
1416             }
1417         } else {
1418             // Save the image to a temp file and invoke the cropper
1419             Uri tempUri = null;
1420             FileOutputStream tempStream = null;
1421             try {
1422                 File path = getFileStreamPath(sTempCropFilename);
1423                 path.delete();
1424                 tempStream = openFileOutput(sTempCropFilename, 0);
1425                 tempStream.write(data);
1426                 tempStream.close();
1427                 tempUri = Uri.fromFile(path);
1428             } catch (FileNotFoundException ex) {
1429                 setResultEx(Activity.RESULT_CANCELED);
1430                 finish();
1431                 return;
1432             } catch (IOException ex) {
1433                 setResultEx(Activity.RESULT_CANCELED);
1434                 finish();
1435                 return;
1436             } finally {
1437                 Util.closeSilently(tempStream);
1438             }
1439 
1440             Bundle newExtras = new Bundle();
1441             if (mCropValue.equals("circle")) {
1442                 newExtras.putString("circleCrop", "true");
1443             }
1444             if (mSaveUri != null) {
1445                 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1446             } else {
1447                 newExtras.putBoolean("return-data", true);
1448             }
1449 
1450             Intent cropIntent = new Intent("com.android.camera.action.CROP");
1451 
1452             cropIntent.setData(tempUri);
1453             cropIntent.putExtras(newExtras);
1454 
1455             startActivityForResult(cropIntent, CROP_MSG);
1456         }
1457     }
1458 
doCancel()1459     private void doCancel() {
1460         setResultEx(RESULT_CANCELED, new Intent());
1461         finish();
1462     }
1463 
1464     @Override
onShutterButtonFocus(boolean pressed)1465     public void onShutterButtonFocus(boolean pressed) {
1466         if (mPausing || collapseCameraControls() || mCameraState == SNAPSHOT_IN_PROGRESS) return;
1467 
1468         // Do not do focus if there is not enough storage.
1469         if (pressed && !canTakePicture()) return;
1470 
1471         if (pressed) {
1472             mFocusManager.onShutterDown();
1473         } else {
1474             mFocusManager.onShutterUp();
1475         }
1476     }
1477 
1478     @Override
onShutterButtonClick()1479     public void onShutterButtonClick() {
1480         if (mPausing || collapseCameraControls()) return;
1481 
1482         // Do not take the picture if there is not enough storage.
1483         if (mPicturesRemaining <= 0) {
1484             Log.i(TAG, "Not enough space or storage not ready. remaining=" + mPicturesRemaining);
1485             return;
1486         }
1487 
1488         Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1489 
1490         // If the user wants to do a snapshot while the previous one is still
1491         // in progress, remember the fact and do it after we finish the previous
1492         // one and re-start the preview. Snapshot in progress also includes the
1493         // state that autofocus is focusing and a picture will be taken when
1494         // focus callback arrives.
1495         if (mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS) {
1496             mSnapshotOnIdle = true;
1497             return;
1498         }
1499 
1500         mSnapshotOnIdle = false;
1501         mFocusManager.doSnap();
1502     }
1503 
1504     private OnScreenHint mStorageHint;
1505 
updateStorageHint()1506     private void updateStorageHint() {
1507         String noStorageText = null;
1508 
1509         if (mPicturesRemaining == Storage.UNAVAILABLE) {
1510             noStorageText = getString(R.string.no_storage);
1511         } else if (mPicturesRemaining == Storage.PREPARING) {
1512             noStorageText = getString(R.string.preparing_sd);
1513         } else if (mPicturesRemaining == Storage.UNKNOWN_SIZE) {
1514             noStorageText = getString(R.string.access_sd_fail);
1515         } else if (mPicturesRemaining < 1L) {
1516             noStorageText = getString(R.string.not_enough_space);
1517         }
1518 
1519         if (noStorageText != null) {
1520             if (mStorageHint == null) {
1521                 mStorageHint = OnScreenHint.makeText(this, noStorageText);
1522             } else {
1523                 mStorageHint.setText(noStorageText);
1524             }
1525             mStorageHint.show();
1526         } else if (mStorageHint != null) {
1527             mStorageHint.cancel();
1528             mStorageHint = null;
1529         }
1530     }
1531 
installIntentFilter()1532     private void installIntentFilter() {
1533         // install an intent filter to receive SD card related events.
1534         IntentFilter intentFilter =
1535                 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1536         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1537         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1538         intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1539         intentFilter.addDataScheme("file");
1540         registerReceiver(mReceiver, intentFilter);
1541         mDidRegister = true;
1542     }
1543 
1544     @Override
doOnResume()1545     protected void doOnResume() {
1546         if (mOpenCameraFail || mCameraDisabled) return;
1547 
1548         mPausing = false;
1549         mJpegPictureCallbackTime = 0;
1550         mZoomValue = 0;
1551 
1552         // Start the preview if it is not started.
1553         if (mCameraState == PREVIEW_STOPPED) {
1554             try {
1555                 mCameraDevice = Util.openCamera(this, mCameraId);
1556                 initializeCapabilities();
1557                 resetExposureCompensation();
1558                 startPreview();
1559                 startFaceDetection();
1560             } catch (CameraHardwareException e) {
1561                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1562                 return;
1563             } catch (CameraDisabledException e) {
1564                 Util.showErrorAndFinish(this, R.string.camera_disabled);
1565                 return;
1566             }
1567         }
1568 
1569         if (mSurfaceHolder != null) {
1570             // If first time initialization is not finished, put it in the
1571             // message queue.
1572             if (!mFirstTimeInitialized) {
1573                 mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1574             } else {
1575                 initializeSecondTime();
1576             }
1577         }
1578         keepScreenOnAwhile();
1579 
1580         if (mCameraState == IDLE) {
1581             mOnResumeTime = SystemClock.uptimeMillis();
1582             mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
1583         }
1584         // Dismiss open menu if exists.
1585         PopupManager.getInstance(this).notifyShowPopup(null);
1586 
1587         if (mCameraSound == null) mCameraSound = new MediaActionSound();
1588     }
1589 
1590     @Override
onPause()1591     protected void onPause() {
1592         mPausing = true;
1593         stopPreview();
1594         // Close the camera now because other activities may need to use it.
1595         closeCamera();
1596         if (mCameraSound != null) {
1597             mCameraSound.release();
1598             mCameraSound = null;
1599         }
1600         resetScreenOn();
1601 
1602         // Clear UI.
1603         collapseCameraControls();
1604         if (mSharePopup != null) mSharePopup.dismiss();
1605         if (mFaceView != null) mFaceView.clear();
1606 
1607         if (mFirstTimeInitialized) {
1608             mOrientationListener.disable();
1609             if (mImageSaver != null) {
1610                 mImageSaver.finish();
1611                 mImageSaver = null;
1612             }
1613             if (!mIsImageCaptureIntent && mThumbnail != null && !mThumbnail.fromFile()) {
1614                 mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
1615             }
1616         }
1617 
1618         if (mDidRegister) {
1619             unregisterReceiver(mReceiver);
1620             mDidRegister = false;
1621         }
1622         if (mLocationManager != null) mLocationManager.recordLocation(false);
1623         updateExposureOnScreenIndicator(0);
1624 
1625         if (mStorageHint != null) {
1626             mStorageHint.cancel();
1627             mStorageHint = null;
1628         }
1629 
1630         // If we are in an image capture intent and has taken
1631         // a picture, we just clear it in onPause.
1632         mJpegImageData = null;
1633 
1634         // Remove the messages in the event queue.
1635         mHandler.removeMessages(FIRST_TIME_INIT);
1636         mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1637         mFocusManager.removeMessages();
1638 
1639         super.onPause();
1640     }
1641 
1642     @Override
onActivityResult( int requestCode, int resultCode, Intent data)1643     protected void onActivityResult(
1644             int requestCode, int resultCode, Intent data) {
1645         switch (requestCode) {
1646             case CROP_MSG: {
1647                 Intent intent = new Intent();
1648                 if (data != null) {
1649                     Bundle extras = data.getExtras();
1650                     if (extras != null) {
1651                         intent.putExtras(extras);
1652                     }
1653                 }
1654                 setResultEx(resultCode, intent);
1655                 finish();
1656 
1657                 File path = getFileStreamPath(sTempCropFilename);
1658                 path.delete();
1659 
1660                 break;
1661             }
1662         }
1663     }
1664 
canTakePicture()1665     private boolean canTakePicture() {
1666         return isCameraIdle() && (mPicturesRemaining > 0);
1667     }
1668 
1669     @Override
autoFocus()1670     public void autoFocus() {
1671         mFocusStartTime = System.currentTimeMillis();
1672         mCameraDevice.autoFocus(mAutoFocusCallback);
1673         setCameraState(FOCUSING);
1674     }
1675 
1676     @Override
cancelAutoFocus()1677     public void cancelAutoFocus() {
1678         mCameraDevice.cancelAutoFocus();
1679         setCameraState(IDLE);
1680         setCameraParameters(UPDATE_PARAM_PREFERENCE);
1681     }
1682 
1683     // Preview area is touched. Handle touch focus.
1684     @Override
onTouch(View v, MotionEvent e)1685     public boolean onTouch(View v, MotionEvent e) {
1686         if (mPausing || mCameraDevice == null || !mFirstTimeInitialized
1687                 || mCameraState == SNAPSHOT_IN_PROGRESS) {
1688             return false;
1689         }
1690 
1691         // Do not trigger touch focus if popup window is opened.
1692         if (collapseCameraControls()) return false;
1693 
1694         // Check if metering area or focus area is supported.
1695         if (!mFocusAreaSupported && !mMeteringAreaSupported) return false;
1696 
1697         return mFocusManager.onTouch(e);
1698     }
1699 
1700     @Override
onBackPressed()1701     public void onBackPressed() {
1702         if (!isCameraIdle()) {
1703             // ignore backs while we're taking a picture
1704             return;
1705         } else if (!collapseCameraControls()) {
1706             super.onBackPressed();
1707         }
1708     }
1709 
1710     @Override
onKeyDown(int keyCode, KeyEvent event)1711     public boolean onKeyDown(int keyCode, KeyEvent event) {
1712         switch (keyCode) {
1713             case KeyEvent.KEYCODE_FOCUS:
1714                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1715                     onShutterButtonFocus(true);
1716                 }
1717                 return true;
1718             case KeyEvent.KEYCODE_CAMERA:
1719                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1720                     onShutterButtonClick();
1721                 }
1722                 return true;
1723             case KeyEvent.KEYCODE_DPAD_CENTER:
1724                 // If we get a dpad center event without any focused view, move
1725                 // the focus to the shutter button and press it.
1726                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1727                     // Start auto-focus immediately to reduce shutter lag. After
1728                     // the shutter button gets the focus, onShutterButtonFocus()
1729                     // will be called again but it is fine.
1730                     if (collapseCameraControls()) return true;
1731                     onShutterButtonFocus(true);
1732                     if (mShutterButton.isInTouchMode()) {
1733                         mShutterButton.requestFocusFromTouch();
1734                     } else {
1735                         mShutterButton.requestFocus();
1736                     }
1737                     mShutterButton.setPressed(true);
1738                 }
1739                 return true;
1740         }
1741 
1742         return super.onKeyDown(keyCode, event);
1743     }
1744 
1745     @Override
onKeyUp(int keyCode, KeyEvent event)1746     public boolean onKeyUp(int keyCode, KeyEvent event) {
1747         switch (keyCode) {
1748             case KeyEvent.KEYCODE_FOCUS:
1749                 if (mFirstTimeInitialized) {
1750                     onShutterButtonFocus(false);
1751                 }
1752                 return true;
1753         }
1754         return super.onKeyUp(keyCode, event);
1755     }
1756 
surfaceChanged(SurfaceHolder holder, int format, int w, int h)1757     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1758         // Make sure we have a surface in the holder before proceeding.
1759         if (holder.getSurface() == null) {
1760             Log.d(TAG, "holder.getSurface() == null");
1761             return;
1762         }
1763 
1764         Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1765 
1766         // We need to save the holder for later use, even when the mCameraDevice
1767         // is null. This could happen if onResume() is invoked after this
1768         // function.
1769         mSurfaceHolder = holder;
1770 
1771         // The mCameraDevice will be null if it fails to connect to the camera
1772         // hardware. In this case we will show a dialog and then finish the
1773         // activity, so it's OK to ignore it.
1774         if (mCameraDevice == null) return;
1775 
1776         // Sometimes surfaceChanged is called after onPause or before onResume.
1777         // Ignore it.
1778         if (mPausing || isFinishing()) return;
1779 
1780         // Set preview display if the surface is being created. Preview was
1781         // already started. Also restart the preview if display rotation has
1782         // changed. Sometimes this happens when the device is held in portrait
1783         // and camera app is opened. Rotation animation takes some time and
1784         // display rotation in onCreate may not be what we want.
1785         if (mCameraState == PREVIEW_STOPPED) {
1786             startPreview();
1787             startFaceDetection();
1788         } else {
1789             if (Util.getDisplayRotation(this) != mDisplayRotation) {
1790                 setDisplayOrientation();
1791             }
1792             if (holder.isCreating()) {
1793                 // Set preview display if the surface is being created and preview
1794                 // was already started. That means preview display was set to null
1795                 // and we need to set it now.
1796                 setPreviewDisplay(holder);
1797             }
1798         }
1799 
1800         // If first time initialization is not finished, send a message to do
1801         // it later. We want to finish surfaceChanged as soon as possible to let
1802         // user see preview first.
1803         if (!mFirstTimeInitialized) {
1804             mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1805         } else {
1806             initializeSecondTime();
1807         }
1808     }
1809 
surfaceCreated(SurfaceHolder holder)1810     public void surfaceCreated(SurfaceHolder holder) {
1811     }
1812 
surfaceDestroyed(SurfaceHolder holder)1813     public void surfaceDestroyed(SurfaceHolder holder) {
1814         stopPreview();
1815         mSurfaceHolder = null;
1816     }
1817 
closeCamera()1818     private void closeCamera() {
1819         if (mCameraDevice != null) {
1820             CameraHolder.instance().release();
1821             mFaceDetectionStarted = false;
1822             mCameraDevice.setZoomChangeListener(null);
1823             mCameraDevice.setFaceDetectionListener(null);
1824             mCameraDevice.setErrorCallback(null);
1825             mCameraDevice = null;
1826             setCameraState(PREVIEW_STOPPED);
1827             mFocusManager.onCameraReleased();
1828         }
1829     }
1830 
setPreviewDisplay(SurfaceHolder holder)1831     private void setPreviewDisplay(SurfaceHolder holder) {
1832         try {
1833             mCameraDevice.setPreviewDisplay(holder);
1834         } catch (Throwable ex) {
1835             closeCamera();
1836             throw new RuntimeException("setPreviewDisplay failed", ex);
1837         }
1838     }
1839 
setDisplayOrientation()1840     private void setDisplayOrientation() {
1841         mDisplayRotation = Util.getDisplayRotation(this);
1842         mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
1843         mCameraDevice.setDisplayOrientation(mDisplayOrientation);
1844         if (mFaceView != null) {
1845             mFaceView.setDisplayOrientation(mDisplayOrientation);
1846         }
1847     }
1848 
startPreview()1849     private void startPreview() {
1850         if (mPausing || isFinishing()) return;
1851 
1852         mFocusManager.resetTouchFocus();
1853 
1854         mCameraDevice.setErrorCallback(mErrorCallback);
1855 
1856         // If we're previewing already, stop the preview first (this will blank
1857         // the screen).
1858         if (mCameraState != PREVIEW_STOPPED) stopPreview();
1859 
1860         setPreviewDisplay(mSurfaceHolder);
1861         setDisplayOrientation();
1862 
1863         if (!mSnapshotOnIdle) {
1864             // If the focus mode is continuous autofocus, call cancelAutoFocus to
1865             // resume it because it may have been paused by autoFocus call.
1866             if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
1867                 mCameraDevice.cancelAutoFocus();
1868             }
1869             mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1870         }
1871         setCameraParameters(UPDATE_PARAM_ALL);
1872 
1873         // Inform the mainthread to go on the UI initialization.
1874         if (mCameraPreviewThread != null) {
1875             synchronized (mCameraPreviewThread) {
1876                 mCameraPreviewThread.notify();
1877             }
1878         }
1879 
1880         try {
1881             Log.v(TAG, "startPreview");
1882             mCameraDevice.startPreview();
1883         } catch (Throwable ex) {
1884             closeCamera();
1885             throw new RuntimeException("startPreview failed", ex);
1886         }
1887 
1888         mZoomState = ZOOM_STOPPED;
1889         setCameraState(IDLE);
1890         mFocusManager.onPreviewStarted();
1891 
1892         if (mSnapshotOnIdle) {
1893             mHandler.post(mDoSnapRunnable);
1894         }
1895     }
1896 
stopPreview()1897     private void stopPreview() {
1898         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1899             Log.v(TAG, "stopPreview");
1900             mCameraDevice.cancelAutoFocus(); // Reset the focus.
1901             mCameraDevice.stopPreview();
1902             mFaceDetectionStarted = false;
1903         }
1904         setCameraState(PREVIEW_STOPPED);
1905         mFocusManager.onPreviewStopped();
1906     }
1907 
isSupported(String value, List<String> supported)1908     private static boolean isSupported(String value, List<String> supported) {
1909         return supported == null ? false : supported.indexOf(value) >= 0;
1910     }
1911 
updateCameraParametersInitialize()1912     private void updateCameraParametersInitialize() {
1913         // Reset preview frame rate to the maximum because it may be lowered by
1914         // video camera application.
1915         List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1916         if (frameRates != null) {
1917             Integer max = Collections.max(frameRates);
1918             mParameters.setPreviewFrameRate(max);
1919         }
1920 
1921         mParameters.setRecordingHint(false);
1922 
1923         // Disable video stabilization. Convenience methods not available in API
1924         // level <= 14
1925         String vstabSupported = mParameters.get("video-stabilization-supported");
1926         if ("true".equals(vstabSupported)) {
1927             mParameters.set("video-stabilization", "false");
1928         }
1929     }
1930 
updateCameraParametersZoom()1931     private void updateCameraParametersZoom() {
1932         // Set zoom.
1933         if (mParameters.isZoomSupported()) {
1934             mParameters.setZoom(mZoomValue);
1935         }
1936     }
1937 
updateCameraParametersPreference()1938     private void updateCameraParametersPreference() {
1939         if (mAeLockSupported) {
1940             mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
1941         }
1942 
1943         if (mAwbLockSupported) {
1944             mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1945         }
1946 
1947         if (mFocusAreaSupported) {
1948             mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1949         }
1950 
1951         if (mMeteringAreaSupported) {
1952             // Use the same area for focus and metering.
1953             mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1954         }
1955 
1956         // Set picture size.
1957         String pictureSize = mPreferences.getString(
1958                 CameraSettings.KEY_PICTURE_SIZE, null);
1959         if (pictureSize == null) {
1960             CameraSettings.initialCameraPictureSize(this, mParameters);
1961         } else {
1962             List<Size> supported = mParameters.getSupportedPictureSizes();
1963             CameraSettings.setCameraPictureSize(
1964                     pictureSize, supported, mParameters);
1965         }
1966 
1967         // Set the preview frame aspect ratio according to the picture size.
1968         Size size = mParameters.getPictureSize();
1969 
1970         mPreviewPanel = findViewById(R.id.frame_layout);
1971         mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
1972         mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
1973 
1974         // Set a preview size that is closest to the viewfinder height and has
1975         // the right aspect ratio.
1976         List<Size> sizes = mParameters.getSupportedPreviewSizes();
1977         Size optimalSize = Util.getOptimalPreviewSize(this,
1978                 sizes, (double) size.width / size.height);
1979         Size original = mParameters.getPreviewSize();
1980         if (!original.equals(optimalSize)) {
1981             mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1982 
1983             // Zoom related settings will be changed for different preview
1984             // sizes, so set and read the parameters to get lastest values
1985             mCameraDevice.setParameters(mParameters);
1986             mParameters = mCameraDevice.getParameters();
1987         }
1988         Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1989 
1990         // Since change scene mode may change supported values,
1991         // Set scene mode first,
1992         mSceneMode = mPreferences.getString(
1993                 CameraSettings.KEY_SCENE_MODE,
1994                 getString(R.string.pref_camera_scenemode_default));
1995         if (isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1996             if (!mParameters.getSceneMode().equals(mSceneMode)) {
1997                 mParameters.setSceneMode(mSceneMode);
1998                 mCameraDevice.setParameters(mParameters);
1999 
2000                 // Setting scene mode will change the settings of flash mode,
2001                 // white balance, and focus mode. Here we read back the
2002                 // parameters, so we can know those settings.
2003                 mParameters = mCameraDevice.getParameters();
2004             }
2005         } else {
2006             mSceneMode = mParameters.getSceneMode();
2007             if (mSceneMode == null) {
2008                 mSceneMode = Parameters.SCENE_MODE_AUTO;
2009             }
2010         }
2011 
2012         // Set JPEG quality.
2013         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2014                 CameraProfile.QUALITY_HIGH);
2015         mParameters.setJpegQuality(jpegQuality);
2016 
2017         // For the following settings, we need to check if the settings are
2018         // still supported by latest driver, if not, ignore the settings.
2019 
2020         // Set exposure compensation
2021         int value = CameraSettings.readExposure(mPreferences);
2022         int max = mParameters.getMaxExposureCompensation();
2023         int min = mParameters.getMinExposureCompensation();
2024         if (value >= min && value <= max) {
2025             mParameters.setExposureCompensation(value);
2026         } else {
2027             Log.w(TAG, "invalid exposure range: " + value);
2028         }
2029 
2030         if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
2031             // Set flash mode.
2032             String flashMode = mPreferences.getString(
2033                     CameraSettings.KEY_FLASH_MODE,
2034                     getString(R.string.pref_camera_flashmode_default));
2035             List<String> supportedFlash = mParameters.getSupportedFlashModes();
2036             if (isSupported(flashMode, supportedFlash)) {
2037                 mParameters.setFlashMode(flashMode);
2038             } else {
2039                 flashMode = mParameters.getFlashMode();
2040                 if (flashMode == null) {
2041                     flashMode = getString(
2042                             R.string.pref_camera_flashmode_no_flash);
2043                 }
2044             }
2045 
2046             // Set white balance parameter.
2047             String whiteBalance = mPreferences.getString(
2048                     CameraSettings.KEY_WHITE_BALANCE,
2049                     getString(R.string.pref_camera_whitebalance_default));
2050             if (isSupported(whiteBalance,
2051                     mParameters.getSupportedWhiteBalance())) {
2052                 mParameters.setWhiteBalance(whiteBalance);
2053             } else {
2054                 whiteBalance = mParameters.getWhiteBalance();
2055                 if (whiteBalance == null) {
2056                     whiteBalance = Parameters.WHITE_BALANCE_AUTO;
2057                 }
2058             }
2059 
2060             // Set focus mode.
2061             mFocusManager.overrideFocusMode(null);
2062             mParameters.setFocusMode(mFocusManager.getFocusMode());
2063         } else {
2064             mFocusManager.overrideFocusMode(mParameters.getFocusMode());
2065         }
2066     }
2067 
2068     // We separate the parameters into several subsets, so we can update only
2069     // the subsets actually need updating. The PREFERENCE set needs extra
2070     // locking because the preference can be changed from GLThread as well.
setCameraParameters(int updateSet)2071     private void setCameraParameters(int updateSet) {
2072         mParameters = mCameraDevice.getParameters();
2073 
2074         if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2075             updateCameraParametersInitialize();
2076         }
2077 
2078         if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2079             updateCameraParametersZoom();
2080         }
2081 
2082         if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2083             updateCameraParametersPreference();
2084         }
2085 
2086         mCameraDevice.setParameters(mParameters);
2087     }
2088 
2089     // If the Camera is idle, update the parameters immediately, otherwise
2090     // accumulate them in mUpdateSet and update later.
setCameraParametersWhenIdle(int additionalUpdateSet)2091     private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2092         mUpdateSet |= additionalUpdateSet;
2093         if (mCameraDevice == null) {
2094             // We will update all the parameters when we open the device, so
2095             // we don't need to do anything now.
2096             mUpdateSet = 0;
2097             return;
2098         } else if (isCameraIdle()) {
2099             setCameraParameters(mUpdateSet);
2100             updateSceneModeUI();
2101             mUpdateSet = 0;
2102         } else {
2103             if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2104                 mHandler.sendEmptyMessageDelayed(
2105                         SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2106             }
2107         }
2108     }
2109 
gotoGallery()2110     private void gotoGallery() {
2111         MenuHelper.gotoCameraImageGallery(this);
2112     }
2113 
isCameraIdle()2114     private boolean isCameraIdle() {
2115         return (mCameraState == IDLE) || (mFocusManager.isFocusCompleted());
2116     }
2117 
isImageCaptureIntent()2118     private boolean isImageCaptureIntent() {
2119         String action = getIntent().getAction();
2120         return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
2121     }
2122 
setupCaptureParams()2123     private void setupCaptureParams() {
2124         Bundle myExtras = getIntent().getExtras();
2125         if (myExtras != null) {
2126             mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2127             mCropValue = myExtras.getString("crop");
2128         }
2129     }
2130 
showPostCaptureAlert()2131     private void showPostCaptureAlert() {
2132         if (mIsImageCaptureIntent) {
2133             Util.fadeOut(mIndicatorControlContainer);
2134             Util.fadeOut(mShutterButton);
2135 
2136             int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2137             for (int id : pickIds) {
2138                 Util.fadeIn(findViewById(id));
2139             }
2140         }
2141     }
2142 
hidePostCaptureAlert()2143     private void hidePostCaptureAlert() {
2144         if (mIsImageCaptureIntent) {
2145             int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2146             for (int id : pickIds) {
2147                 Util.fadeOut(findViewById(id));
2148             }
2149 
2150             Util.fadeIn(mShutterButton);
2151             Util.fadeIn(mIndicatorControlContainer);
2152         }
2153     }
2154 
2155     @Override
onPrepareOptionsMenu(Menu menu)2156     public boolean onPrepareOptionsMenu(Menu menu) {
2157         super.onPrepareOptionsMenu(menu);
2158         // Only show the menu when camera is idle.
2159         for (int i = 0; i < menu.size(); i++) {
2160             menu.getItem(i).setVisible(isCameraIdle());
2161         }
2162 
2163         return true;
2164     }
2165 
2166     @Override
onCreateOptionsMenu(Menu menu)2167     public boolean onCreateOptionsMenu(Menu menu) {
2168         super.onCreateOptionsMenu(menu);
2169 
2170         if (mIsImageCaptureIntent) {
2171             // No options menu for attach mode.
2172             return false;
2173         } else {
2174             addBaseMenuItems(menu);
2175         }
2176         return true;
2177     }
2178 
addBaseMenuItems(Menu menu)2179     private void addBaseMenuItems(Menu menu) {
2180         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_VIDEO, new Runnable() {
2181             public void run() {
2182                 switchToOtherMode(ModePicker.MODE_VIDEO);
2183             }
2184         });
2185         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
2186             public void run() {
2187                 switchToOtherMode(ModePicker.MODE_PANORAMA);
2188             }
2189         });
2190 
2191         if (mNumberOfCameras > 1) {
2192             menu.add(R.string.switch_camera_id)
2193                     .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2194                 public boolean onMenuItemClick(MenuItem item) {
2195                     CameraSettings.writePreferredCameraId(mPreferences,
2196                             (mCameraId + 1) % mNumberOfCameras);
2197                     onSharedPreferenceChanged();
2198                     return true;
2199                 }
2200             }).setIcon(android.R.drawable.ic_menu_camera);
2201         }
2202     }
2203 
switchToOtherMode(int mode)2204     private boolean switchToOtherMode(int mode) {
2205         if (isFinishing()) return false;
2206         if (mImageSaver != null) mImageSaver.waitDone();
2207         MenuHelper.gotoMode(mode, Camera.this);
2208         mHandler.removeMessages(FIRST_TIME_INIT);
2209         finish();
2210         return true;
2211     }
2212 
onModeChanged(int mode)2213     public boolean onModeChanged(int mode) {
2214         if (mode != ModePicker.MODE_CAMERA) {
2215             return switchToOtherMode(mode);
2216         } else {
2217             return true;
2218         }
2219     }
2220 
onSharedPreferenceChanged()2221     public void onSharedPreferenceChanged() {
2222         // ignore the events after "onPause()"
2223         if (mPausing) return;
2224 
2225         boolean recordLocation = RecordLocationPreference.get(
2226                 mPreferences, getContentResolver());
2227         mLocationManager.recordLocation(recordLocation);
2228 
2229         int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
2230         if (mCameraId != cameraId) {
2231             // Restart the activity to have a crossfade animation.
2232             // TODO: Use SurfaceTexture to implement a better and faster
2233             // animation.
2234             if (mIsImageCaptureIntent) {
2235                 // If the intent is camera capture, stay in camera capture mode.
2236                 MenuHelper.gotoCameraMode(this, getIntent());
2237             } else {
2238                 MenuHelper.gotoCameraMode(this);
2239             }
2240 
2241             finish();
2242         } else {
2243             setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2244         }
2245 
2246         updateOnScreenIndicators();
2247     }
2248 
2249     @Override
onUserInteraction()2250     public void onUserInteraction() {
2251         super.onUserInteraction();
2252         keepScreenOnAwhile();
2253     }
2254 
resetScreenOn()2255     private void resetScreenOn() {
2256         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2257         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2258     }
2259 
keepScreenOnAwhile()2260     private void keepScreenOnAwhile() {
2261         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2262         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2263         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2264     }
2265 
onRestorePreferencesClicked()2266     public void onRestorePreferencesClicked() {
2267         if (mPausing) return;
2268         Runnable runnable = new Runnable() {
2269             public void run() {
2270                 restorePreferences();
2271             }
2272         };
2273         mRotateDialog.showAlertDialog(
2274                 getString(R.string.confirm_restore_title),
2275                 getString(R.string.confirm_restore_message),
2276                 getString(android.R.string.ok), runnable,
2277                 getString(android.R.string.cancel), null);
2278     }
2279 
restorePreferences()2280     private void restorePreferences() {
2281         // Reset the zoom. Zoom value is not stored in preference.
2282         if (mParameters.isZoomSupported()) {
2283             mZoomValue = 0;
2284             setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2285             mZoomControl.setZoomIndex(0);
2286         }
2287         if (mIndicatorControlContainer != null) {
2288             mIndicatorControlContainer.dismissSettingPopup();
2289             CameraSettings.restorePreferences(Camera.this, mPreferences,
2290                     mParameters);
2291             mIndicatorControlContainer.reloadPreferences();
2292             onSharedPreferenceChanged();
2293         }
2294     }
2295 
onOverriddenPreferencesClicked()2296     public void onOverriddenPreferencesClicked() {
2297         if (mPausing) return;
2298         if (mNotSelectableToast == null) {
2299             String str = getResources().getString(R.string.not_selectable_in_scene_mode);
2300             mNotSelectableToast = Toast.makeText(Camera.this, str, Toast.LENGTH_SHORT);
2301         }
2302         mNotSelectableToast.show();
2303     }
2304 
showSharePopup()2305     private void showSharePopup() {
2306         mImageSaver.waitDone();
2307         Uri uri = mThumbnail.getUri();
2308         if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
2309             // SharePopup window takes the mPreviewPanel as its size reference.
2310             mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
2311                     mOrientationCompensation, mPreviewPanel);
2312         }
2313         mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
2314     }
2315 
2316     @Override
onFaceDetection(Face[] faces, android.hardware.Camera camera)2317     public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
2318         mFaceView.setFaces(faces);
2319     }
2320 
showTapToFocusToast()2321     private void showTapToFocusToast() {
2322         new RotateTextToast(this, R.string.tap_to_focus, mOrientation).show();
2323         // Clear the preference.
2324         Editor editor = mPreferences.edit();
2325         editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
2326         editor.apply();
2327     }
2328 
initializeCapabilities()2329     private void initializeCapabilities() {
2330         mInitialParams = mCameraDevice.getParameters();
2331         mFocusManager.initializeParameters(mInitialParams);
2332         mFocusAreaSupported = (mInitialParams.getMaxNumFocusAreas() > 0
2333                 && isSupported(Parameters.FOCUS_MODE_AUTO,
2334                         mInitialParams.getSupportedFocusModes()));
2335         mMeteringAreaSupported = (mInitialParams.getMaxNumMeteringAreas() > 0);
2336         mAeLockSupported = mInitialParams.isAutoExposureLockSupported();
2337         mAwbLockSupported = mInitialParams.isAutoWhiteBalanceLockSupported();
2338     }
2339 }
2340