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