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