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