| The Android Open Source Project | 0eaec58 | 2008-12-17 18:05:54 -0800 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2007 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.camera; |
| 18 | |
| 19 | import java.io.File; |
| 20 | import java.util.ArrayList; |
| 21 | import java.io.IOException; |
| 22 | |
| 23 | import android.app.Activity; |
| 24 | import android.app.ProgressDialog; |
| 25 | import android.content.ActivityNotFoundException; |
| 26 | import android.content.BroadcastReceiver; |
| 27 | import android.content.ContentResolver; |
| 28 | import android.content.ContentValues; |
| 29 | import android.content.Context; |
| 30 | import android.content.Intent; |
| 31 | import android.content.IntentFilter; |
| 32 | import android.content.SharedPreferences; |
| 33 | import android.content.res.Resources; |
| 34 | import android.graphics.Bitmap; |
| 35 | import android.graphics.drawable.ColorDrawable; |
| 36 | import android.location.LocationManager; |
| 37 | import android.media.MediaMetadataRetriever; |
| 38 | import android.media.MediaRecorder; |
| 39 | import android.net.Uri; |
| 40 | import android.os.Bundle; |
| 41 | import android.os.Environment; |
| 42 | import android.os.Handler; |
| 43 | import android.os.Message; |
| 44 | import android.os.StatFs; |
| 45 | import android.os.SystemClock; |
| 46 | import android.preference.PreferenceManager; |
| 47 | import android.provider.MediaStore.Images; |
| 48 | import android.provider.MediaStore.Video; |
| 49 | import android.text.format.DateFormat; |
| 50 | import android.util.Log; |
| 51 | import android.view.KeyEvent; |
| 52 | import android.view.Menu; |
| 53 | import android.view.MenuItem; |
| 54 | import android.view.MotionEvent; |
| 55 | import android.view.SurfaceHolder; |
| 56 | import android.view.SurfaceView; |
| 57 | import android.view.View; |
| 58 | import android.view.Window; |
| 59 | import android.view.WindowManager; |
| 60 | import android.view.MenuItem.OnMenuItemClickListener; |
| 61 | import android.view.animation.Animation; |
| 62 | import android.widget.ImageView; |
| 63 | import android.widget.TextView; |
| 64 | import android.widget.Toast; |
| 65 | import android.widget.RelativeLayout.LayoutParams; |
| 66 | |
| 67 | public class VideoCamera extends Activity implements View.OnClickListener, SurfaceHolder.Callback { |
| 68 | |
| 69 | private static final String TAG = "videocamera"; |
| 70 | |
| 71 | private static final boolean DEBUG = true; |
| 72 | private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = DEBUG && true; |
| 73 | private static final boolean DEBUG_DO_NOT_REUSE_MEDIA_RECORDER = DEBUG && true; |
| 74 | |
| 75 | private static final int KEEP = 2; |
| 76 | private static final int CLEAR_SCREEN_DELAY = 4; |
| 77 | private static final int UPDATE_RECORD_TIME = 5; |
| 78 | private static final int RESTART_PREVIEW = 6; |
| 79 | |
| 80 | private static final int SCREEN_DELAY = 2 * 60 * 1000; |
| 81 | private static final int POST_PICTURE_ALERT_TIMEOUT = 6 * 1000; |
| 82 | |
| 83 | private static final int NO_STORAGE_ERROR = -1; |
| 84 | private static final int CANNOT_STAT_ERROR = -2; |
| 85 | |
| 86 | public static final int MENU_SWITCH_TO_VIDEO = 0; |
| 87 | public static final int MENU_SWITCH_TO_CAMERA = 1; |
| 88 | public static final int MENU_SETTINGS = 6; |
| 89 | public static final int MENU_GALLERY_PHOTOS = 7; |
| 90 | public static final int MENU_GALLERY_VIDEOS = 8; |
| 91 | public static final int MENU_SAVE_SELECT_PHOTOS = 30; |
| 92 | public static final int MENU_SAVE_NEW_PHOTO = 31; |
| 93 | public static final int MENU_SAVE_SELECTVIDEO = 32; |
| 94 | public static final int MENU_SAVE_TAKE_NEW_VIDEO = 33; |
| 95 | public static final int MENU_SAVE_GALLERY_PHOTO = 34; |
| 96 | public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35; |
| 97 | public static final int MENU_SAVE_CAMERA_DONE = 36; |
| 98 | public static final int MENU_SAVE_CAMERA_VIDEO_DONE = 37; |
| 99 | |
| 100 | Toast mToast; |
| 101 | SharedPreferences mPreferences; |
| 102 | |
| 103 | private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f; |
| 104 | VideoPreview mVideoPreview; |
| 105 | SurfaceHolder mSurfaceHolder = null; |
| 106 | ImageView mBlackout = null; |
| 107 | ImageView mVideoFrame; |
| 108 | Bitmap mVideoFrameBitmap; |
| 109 | |
| 110 | private MediaRecorder mMediaRecorder; |
| 111 | private boolean mMediaRecorderRecording = false; |
| 112 | private long mRecordingStartTime; |
| 113 | private String mCurrentVideoFilename; |
| 114 | private Uri mCurrentVideoUri; |
| 115 | |
| 116 | boolean mPausing = false; |
| 117 | |
| 118 | static ContentResolver mContentResolver; |
| 119 | boolean mDidRegister = false; |
| 120 | |
| 121 | int mCurrentZoomIndex = 0; |
| 122 | |
| 123 | private ImageView mModeIndicatorView; |
| 124 | private ImageView mRecordingIndicatorView; |
| 125 | private TextView mRecordingTimeView; |
| 126 | |
| 127 | ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); |
| 128 | |
| 129 | View mPostPictureAlert; |
| 130 | LocationManager mLocationManager = null; |
| 131 | |
| 132 | private int mPicturesRemaining; |
| 133 | |
| 134 | private Handler mHandler = new MainHandler(); |
| 135 | |
| 136 | private void cancelSavingNotification() { |
| 137 | if (mToast != null) { |
| 138 | mToast.cancel(); |
| 139 | mToast = null; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | /** This Handler is used to post message back onto the main thread of the application */ |
| 144 | private class MainHandler extends Handler { |
| 145 | @Override |
| 146 | public void handleMessage(Message msg) { |
| 147 | switch (msg.what) { |
| 148 | case KEEP: { |
| 149 | keep(); |
| 150 | |
| 151 | if (msg.obj != null) { |
| 152 | mHandler.post((Runnable)msg.obj); |
| 153 | } |
| 154 | break; |
| 155 | } |
| 156 | |
| 157 | case CLEAR_SCREEN_DELAY: { |
| 158 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| 159 | break; |
| 160 | } |
| 161 | |
| 162 | case UPDATE_RECORD_TIME: { |
| 163 | if (mMediaRecorderRecording) { |
| 164 | long now = SystemClock.uptimeMillis(); |
| 165 | long delta = now - mRecordingStartTime; |
| 166 | long seconds = delta / 1000; |
| 167 | long minutes = seconds / 60; |
| 168 | long remainderSeconds = seconds - (minutes * 60); |
| 169 | |
| 170 | String secondsString = Long.toString(remainderSeconds); |
| 171 | if (secondsString.length() < 2) { |
| 172 | secondsString = "0" + secondsString; |
| 173 | } |
| 174 | String minutesString = Long.toString(minutes); |
| 175 | if (minutesString.length() < 2) { |
| 176 | minutesString = "0" + minutesString; |
| 177 | } |
| 178 | String text = minutesString + ":" + secondsString; |
| 179 | mRecordingTimeView.setText(text); |
| 180 | mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000); |
| 181 | } |
| 182 | break; |
| 183 | } |
| 184 | |
| 185 | case RESTART_PREVIEW: |
| 186 | hideVideoFrameAndStartPreview(); |
| 187 | break; |
| 188 | |
| 189 | default: |
| 190 | Log.v(TAG, "Unhandled message: " + msg.what); |
| 191 | break; |
| 192 | } |
| 193 | } |
| 194 | }; |
| 195 | |
| 196 | private final BroadcastReceiver mReceiver = new BroadcastReceiver() { |
| 197 | @Override |
| 198 | public void onReceive(Context context, Intent intent) { |
| 199 | String action = intent.getAction(); |
| 200 | if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { |
| 201 | // SD card available |
| 202 | // TODO put up a "please wait" message |
| 203 | // TODO also listen for the media scanner finished message |
| 204 | showStorageToast(); |
| 205 | } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { |
| 206 | // SD card unavailable |
| 207 | showStorageToast(); |
| 208 | } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { |
| 209 | Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000); |
| 210 | } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { |
| 211 | showStorageToast(); |
| 212 | } |
| 213 | } |
| 214 | }; |
| 215 | |
| 216 | static private String createName(long dateTaken) { |
| 217 | return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); |
| 218 | } |
| 219 | |
| 220 | private void postAfterKeep(final Runnable r) { |
| 221 | Message msg = mHandler.obtainMessage(KEEP); |
| 222 | msg.obj = r; |
| 223 | msg.sendToTarget(); |
| 224 | } |
| 225 | |
| 226 | /** Called with the activity is first created. */ |
| 227 | @Override |
| 228 | public void onCreate(Bundle icicle) { |
| 229 | super.onCreate(icicle); |
| 230 | |
| 231 | mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); |
| 232 | |
| 233 | mPreferences = PreferenceManager.getDefaultSharedPreferences(this); |
| 234 | mContentResolver = getContentResolver(); |
| 235 | |
| 236 | //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); |
| 237 | requestWindowFeature(Window.FEATURE_PROGRESS); |
| 238 | |
| 239 | Window win = getWindow(); |
| 240 | win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| 241 | setContentView(R.layout.video_camera); |
| 242 | |
| 243 | mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview); |
| 244 | mVideoPreview.setAspectRatio(VIDEO_ASPECT_RATIO); |
| 245 | |
| 246 | // don't set mSurfaceHolder here. We have it set ONLY within |
| 247 | // surfaceCreated / surfaceDestroyed, other parts of the code |
| 248 | // assume that when it is set, the surface is also set. |
| 249 | SurfaceHolder holder = mVideoPreview.getHolder(); |
| 250 | holder.addCallback(this); |
| 251 | holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); |
| 252 | |
| 253 | mBlackout = (ImageView) findViewById(R.id.blackout); |
| 254 | mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); |
| 255 | |
| 256 | mPostPictureAlert = findViewById(R.id.post_picture_panel); |
| 257 | View b; |
| 258 | |
| 259 | b = findViewById(R.id.play); |
| 260 | b.setOnClickListener(this); |
| 261 | |
| 262 | b = findViewById(R.id.share); |
| 263 | b.setOnClickListener(this); |
| 264 | |
| 265 | b = findViewById(R.id.discard); |
| 266 | b.setOnClickListener(this); |
| 267 | |
| 268 | mModeIndicatorView = (ImageView) findViewById(R.id.mode_indicator); |
| 269 | mRecordingIndicatorView = (ImageView) findViewById(R.id.recording_indicator); |
| 270 | mRecordingTimeView = (TextView) findViewById(R.id.recording_time); |
| 271 | mVideoFrame = (ImageView) findViewById(R.id.video_frame); |
| 272 | } |
| 273 | |
| 274 | @Override |
| 275 | public void onStart() { |
| 276 | super.onStart(); |
| 277 | |
| 278 | final View hintView = findViewById(R.id.hint_toast); |
| 279 | if (hintView != null) |
| 280 | hintView.setVisibility(View.GONE); |
| 281 | |
| 282 | Thread t = new Thread(new Runnable() { |
| 283 | public void run() { |
| 284 | final boolean storageOK = calculatePicturesRemaining() > 0; |
| 285 | if (hintView == null) |
| 286 | return; |
| 287 | |
| 288 | if (storageOK) { |
| 289 | mHandler.post(new Runnable() { |
| 290 | public void run() { |
| 291 | hintView.setVisibility(View.VISIBLE); |
| 292 | } |
| 293 | }); |
| 294 | mHandler.postDelayed(new Runnable() { |
| 295 | public void run() { |
| 296 | Animation a = new android.view.animation.AlphaAnimation(1F, 0F); |
| 297 | a.setDuration(500); |
| 298 | a.startNow(); |
| 299 | hintView.setAnimation(a); |
| 300 | hintView.setVisibility(View.GONE); |
| 301 | } |
| 302 | }, 3000); |
| 303 | } else { |
| 304 | mHandler.post(new Runnable() { |
| 305 | public void run() { |
| 306 | hintView.setVisibility(View.GONE); |
| 307 | showStorageToast(); |
| 308 | } |
| 309 | }); |
| 310 | } |
| 311 | } |
| 312 | }); |
| 313 | t.start(); |
| 314 | } |
| 315 | |
| 316 | public void onClick(View v) { |
| 317 | switch (v.getId()) { |
| 318 | case R.id.discard: { |
| 319 | File f = new File(mCurrentVideoFilename); |
| 320 | f.delete(); |
| 321 | mContentResolver.delete(mCurrentVideoUri, null, null); |
| 322 | |
| 323 | hideVideoFrameAndStartPreview(); |
| 324 | break; |
| 325 | } |
| 326 | |
| 327 | case R.id.share: { |
| 328 | Intent intent = new Intent(); |
| 329 | intent.setAction(Intent.ACTION_SEND); |
| 330 | intent.setType("video/3gpp"); |
| 331 | intent.putExtra(Intent.EXTRA_STREAM, mCurrentVideoUri); |
| 332 | try { |
| 333 | startActivity(Intent.createChooser(intent, getText(R.string.sendVideo))); |
| 334 | } catch (android.content.ActivityNotFoundException ex) { |
| 335 | Toast.makeText(VideoCamera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); |
| 336 | } |
| 337 | |
| 338 | break; |
| 339 | } |
| 340 | |
| 341 | case R.id.play: { |
| 342 | Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); |
| 343 | try { |
| 344 | startActivity(intent); |
| 345 | } catch (android.content.ActivityNotFoundException ex) { |
| 346 | Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); |
| 347 | } |
| 348 | break; |
| 349 | } |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | private void showStorageToast() { |
| 354 | String noStorageText = null; |
| 355 | int remaining = calculatePicturesRemaining(); |
| 356 | |
| 357 | if (remaining == NO_STORAGE_ERROR) { |
| 358 | noStorageText = getString(R.string.no_storage); |
| 359 | } else if (remaining < 1) { |
| 360 | noStorageText = getString(R.string.not_enough_space); |
| 361 | } |
| 362 | |
| 363 | if (noStorageText != null) { |
| 364 | Toast.makeText(this, noStorageText, 5000).show(); |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | @Override |
| 369 | public void onResume() { |
| 370 | super.onResume(); |
| 371 | mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); |
| 372 | |
| 373 | mPausing = false; |
| 374 | |
| 375 | // install an intent filter to receive SD card related events. |
| 376 | IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); |
| 377 | intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); |
| 378 | intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); |
| 379 | intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| 380 | intentFilter.addDataScheme("file"); |
| 381 | registerReceiver(mReceiver, intentFilter); |
| 382 | mDidRegister = true; |
| 383 | |
| 384 | mBlackout.setVisibility(View.INVISIBLE); |
| 385 | if (mVideoFrameBitmap == null) { |
| 386 | initializeVideo(); |
| 387 | } else { |
| 388 | showPostRecordingAlert(); |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | @Override |
| 393 | public void onStop() { |
| 394 | Log.v(TAG, "onStop"); |
| 395 | stopVideoRecording(); |
| 396 | keep(); |
| 397 | mHandler.removeMessages(CLEAR_SCREEN_DELAY); |
| 398 | super.onStop(); |
| 399 | } |
| 400 | |
| 401 | @Override |
| 402 | protected void onPause() { |
| 403 | Log.v(TAG, "onPause"); |
| 404 | stopVideoRecording(); |
| 405 | keep(); |
| 406 | hidePostPictureAlert(); |
| 407 | |
| 408 | mPausing = true; |
| 409 | |
| 410 | if (mDidRegister) { |
| 411 | unregisterReceiver(mReceiver); |
| 412 | mDidRegister = false; |
| 413 | } |
| 414 | |
| 415 | super.onPause(); |
| 416 | } |
| 417 | |
| 418 | @Override |
| 419 | public boolean onKeyDown(int keyCode, KeyEvent event) { |
| 420 | mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); |
| 421 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| 422 | |
| 423 | switch (keyCode) { |
| 424 | case KeyEvent.KEYCODE_BACK: |
| 425 | if (mMediaRecorderRecording) { |
| 426 | Log.v(TAG, "onKeyBack"); |
| 427 | stopVideoRecordingAndDisplayDialog(); |
| 428 | return true; |
| 429 | } else if(isPostRecordingAlertVisible()) { |
| 430 | hideVideoFrameAndStartPreview(); |
| 431 | return true; |
| 432 | } |
| 433 | break; |
| 434 | case KeyEvent.KEYCODE_FOCUS: |
| 435 | return true; |
| 436 | case KeyEvent.KEYCODE_CAMERA: |
| 437 | case KeyEvent.KEYCODE_DPAD_CENTER: |
| 438 | if (event.getRepeatCount() == 0) { |
| 439 | if (!mMediaRecorderRecording) { |
| 440 | startVideoRecording(); |
| 441 | } else { |
| 442 | stopVideoRecordingAndDisplayDialog(); |
| 443 | } |
| 444 | return true; |
| 445 | } |
| 446 | return true; |
| 447 | case KeyEvent.KEYCODE_MENU: |
| 448 | if (mMediaRecorderRecording) { |
| 449 | stopVideoRecordingAndDisplayDialog(); |
| 450 | return true; |
| 451 | } |
| 452 | hideVideoFrameAndStartPreview(); |
| 453 | break; |
| 454 | } |
| 455 | |
| 456 | return super.onKeyDown(keyCode, event); |
| 457 | } |
| 458 | |
| 459 | @Override |
| 460 | public boolean onTrackballEvent(MotionEvent event) { |
| 461 | cancelRestartPreviewTimeout(); |
| 462 | return super.onTrackballEvent(event); |
| 463 | } |
| 464 | |
| 465 | public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { |
| 466 | stopVideoRecording(); |
| 467 | initializeVideo(); |
| 468 | } |
| 469 | |
| 470 | public void surfaceCreated(SurfaceHolder holder) { |
| 471 | mSurfaceHolder = holder; |
| 472 | } |
| 473 | |
| 474 | public void surfaceDestroyed(SurfaceHolder holder) { |
| 475 | mSurfaceHolder = null; |
| 476 | } |
| 477 | |
| 478 | void gotoGallery() { |
| 479 | Uri target = Video.Media.INTERNAL_CONTENT_URI; |
| 480 | Intent intent = new Intent(Intent.ACTION_VIEW, target); |
| 481 | try { |
| 482 | startActivity(intent); |
| 483 | } catch (ActivityNotFoundException e) { |
| 484 | Log.e(TAG, "Could not start gallery activity", e); |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | void keep() { |
| 489 | cancelSavingNotification(); |
| 490 | }; |
| 491 | |
| 492 | void toss() { |
| 493 | cancelSavingNotification(); |
| 494 | }; |
| 495 | |
| 496 | @Override |
| 497 | public boolean onPrepareOptionsMenu(Menu menu) { |
| 498 | super.onPrepareOptionsMenu(menu); |
| 499 | |
| 500 | for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) { |
| 501 | if (i != MenuHelper.GENERIC_ITEM) { |
| 502 | menu.setGroupVisible(i, false); |
| 503 | } |
| 504 | } |
| 505 | |
| 506 | menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true); |
| 507 | |
| 508 | return true; |
| 509 | } |
| 510 | |
| 511 | @Override |
| 512 | public boolean onCreateOptionsMenu(Menu menu) { |
| 513 | super.onCreateOptionsMenu(menu); |
| 514 | |
| 515 | addBaseMenuItems(menu); |
| 516 | MenuHelper.addImageMenuItems( |
| 517 | menu, |
| 518 | MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU, |
| 519 | false, |
| 520 | VideoCamera.this, |
| 521 | mHandler, |
| 522 | |
| 523 | // Handler for deletion |
| 524 | new Runnable() { |
| 525 | public void run() { |
| 526 | // What do we do here? |
| 527 | // mContentResolver.delete(uri, null, null); |
| 528 | } |
| 529 | }, |
| 530 | new MenuHelper.MenuInvoker() { |
| 531 | public void run(final MenuHelper.MenuCallback cb) { |
| 532 | } |
| 533 | }); |
| 534 | |
| 535 | MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, |
| 536 | R.string.camera_gallery_photos_text).setOnMenuItemClickListener( |
| 537 | new MenuItem.OnMenuItemClickListener() { |
| 538 | public boolean onMenuItemClick(MenuItem item) { |
| 539 | postAfterKeep(new Runnable() { |
| 540 | public void run() { |
| 541 | gotoGallery(); |
| 542 | } |
| 543 | }); |
| 544 | return true; |
| 545 | } |
| 546 | }); |
| 547 | gallery.setIcon(android.R.drawable.ic_menu_gallery); |
| 548 | return true; |
| 549 | } |
| 550 | |
| 551 | private int calculatePicturesRemaining() { |
| 552 | try { |
| 553 | if (!ImageManager.hasStorage()) { |
| 554 | mPicturesRemaining = NO_STORAGE_ERROR; |
| 555 | } else { |
| 556 | String storageDirectory = Environment.getExternalStorageDirectory().toString(); |
| 557 | StatFs stat = new StatFs(storageDirectory); |
| 558 | float remaining = ((float)stat.getAvailableBlocks() * (float)stat.getBlockSize()) / 400000F; |
| 559 | mPicturesRemaining = (int)remaining; |
| 560 | } |
| 561 | } catch (Exception ex) { |
| 562 | // if we can't stat the filesystem then we don't know how many |
| 563 | // pictures are remaining. it might be zero but just leave it |
| 564 | // blank since we really don't know. |
| 565 | mPicturesRemaining = CANNOT_STAT_ERROR; |
| 566 | } |
| 567 | return mPicturesRemaining; |
| 568 | } |
| 569 | |
| 570 | private void initializeVideo() { |
| 571 | Log.v(TAG, "initializeVideo"); |
| 572 | releaseMediaRecorder(); |
| 573 | |
| 574 | if (mSurfaceHolder == null) { |
| 575 | Log.v(TAG, "SurfaceHolder is null"); |
| 576 | return; |
| 577 | } |
| 578 | |
| 579 | mMediaRecorder = new MediaRecorder(); |
| 580 | |
| 581 | if (DEBUG_SUPPRESS_AUDIO_RECORDING) { |
| 582 | Log.v(TAG, "DEBUG_SUPPRESS_AUDIO_RECORDING is true."); |
| 583 | } else { |
| 584 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); |
| 585 | } |
| 586 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); |
| 587 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); |
| 588 | Log.v(TAG, "before setOutputFile"); |
| 589 | createVideoPath(); |
| 590 | mMediaRecorder.setOutputFile(mCurrentVideoFilename); |
| 591 | Boolean videoQualityLow = getIntPreference("pref_camera_videoquality_key") == 0; |
| 592 | |
| 593 | // Use the same frame rate for both, since internally |
| 594 | // if the frame rate is too large, it can cause camera to become |
| 595 | // unstable. We need to fix the MediaRecorder to disable the support |
| 596 | // of setting frame rate for now. |
| 597 | mMediaRecorder.setVideoFrameRate(20); |
| 598 | if (videoQualityLow) { |
| 599 | mMediaRecorder.setVideoSize(176,144); |
| 600 | } else { |
| 601 | mMediaRecorder.setVideoSize(352,288); |
| 602 | } |
| 603 | mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); |
| 604 | if (!DEBUG_SUPPRESS_AUDIO_RECORDING) { |
| 605 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); |
| 606 | } |
| 607 | Log.v(TAG, "before setPreviewDisplay"); |
| 608 | mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); |
| 609 | try { |
| 610 | mMediaRecorder.prepare(); |
| 611 | } catch (IOException exception) { |
| 612 | Log.e(TAG, "prepare failed for " + mCurrentVideoFilename); |
| 613 | releaseMediaRecorder(); |
| 614 | // TODO: add more exception handling logic here |
| 615 | return; |
| 616 | } |
| 617 | mMediaRecorderRecording = false; |
| 618 | } |
| 619 | |
| 620 | private void releaseMediaRecorder() { |
| 621 | Log.v(TAG, "Releasing media recorder."); |
| 622 | if (mMediaRecorder != null) { |
| 623 | mMediaRecorder.reset(); |
| 624 | mMediaRecorder.release(); |
| 625 | mMediaRecorder = null; |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | private void restartPreview() { |
| 630 | if (DEBUG_DO_NOT_REUSE_MEDIA_RECORDER) { |
| 631 | Log.v(TAG, "DEBUG_DO_NOT_REUSE_MEDIA_RECORDER recreating mMediaRecorder."); |
| 632 | initializeVideo(); |
| 633 | } else { |
| 634 | try { |
| 635 | mMediaRecorder.prepare(); |
| 636 | } catch (IOException exception) { |
| 637 | Log.e(TAG, "prepare failed for " + mCurrentVideoFilename); |
| 638 | releaseMediaRecorder(); |
| 639 | // TODO: add more exception handling logic here |
| 640 | } |
| 641 | } |
| 642 | } |
| 643 | |
| 644 | private int getIntPreference(String key) { |
| 645 | String s = mPreferences.getString(key, "0"); |
| 646 | return Integer.parseInt(s); |
| 647 | } |
| 648 | |
| 649 | private void createVideoPath() { |
| 650 | long dateTaken = System.currentTimeMillis(); |
| 651 | String title = createName(dateTaken); |
| 652 | String displayName = title + ".3gp"; // Used when emailing. |
| 653 | String filename = ImageManager.CAMERA_IMAGE_BUCKET_NAME + "/" |
| 654 | + Long.toString(dateTaken) + ".3gp"; |
| 655 | ContentValues values = new ContentValues(7); |
| 656 | values.put(Video.Media.TITLE, title); |
| 657 | values.put(Video.Media.DISPLAY_NAME, displayName); |
| 658 | values.put(Video.Media.DESCRIPTION, ""); |
| 659 | values.put(Video.Media.DATE_TAKEN, dateTaken); |
| 660 | values.put(Video.Media.MIME_TYPE, "video/3gpp"); |
| 661 | values.put(Video.Media.DATA, filename); |
| 662 | Uri videoTable = Uri.parse("content://media/external/video/media"); |
| 663 | Uri item = mContentResolver.insert(videoTable, values); |
| 664 | mCurrentVideoFilename = filename; |
| 665 | mCurrentVideoUri = item; |
| 666 | } |
| 667 | |
| 668 | private void addBaseMenuItems(Menu menu) { |
| 669 | MenuHelper.addSwitchModeMenuItem(menu, this, false); |
| 670 | { |
| 671 | MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| 672 | public boolean onMenuItemClick(MenuItem item) { |
| 673 | gotoGallery(); |
| 674 | return true; |
| 675 | } |
| 676 | }); |
| 677 | gallery.setIcon(android.R.drawable.ic_menu_gallery); |
| 678 | mGalleryItems.add(gallery); |
| 679 | } |
| 680 | { |
| 681 | MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| 682 | public boolean onMenuItemClick(MenuItem item) { |
| 683 | gotoGallery(); |
| 684 | return true; |
| 685 | } |
| 686 | }); |
| 687 | gallery.setIcon(android.R.drawable.ic_menu_gallery); |
| 688 | mGalleryItems.add(gallery); |
| 689 | } |
| 690 | |
| 691 | MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| 692 | public boolean onMenuItemClick(MenuItem item) { |
| 693 | Intent intent = new Intent(); |
| 694 | intent.setClass(VideoCamera.this, CameraSettings.class); |
| 695 | startActivity(intent); |
| 696 | return true; |
| 697 | } |
| 698 | }); |
| 699 | item.setIcon(android.R.drawable.ic_menu_preferences); |
| 700 | } |
| 701 | |
| 702 | private void startVideoRecording() { |
| 703 | Log.v(TAG, "startVideoRecording"); |
| 704 | if (!mMediaRecorderRecording) { |
| 705 | |
| 706 | // Check mMediaRecorder to see whether it is initialized or not. |
| 707 | if (mMediaRecorder == null) { |
| 708 | initializeVideo(); |
| 709 | } |
| 710 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| 711 | try { |
| 712 | mMediaRecorder.start(); // Recording is now started |
| 713 | } catch (RuntimeException e) { |
| 714 | Log.e(TAG, "Could not start media recorder. ", e); |
| 715 | return; |
| 716 | } |
| 717 | mMediaRecorderRecording = true; |
| 718 | mRecordingStartTime = SystemClock.uptimeMillis(); |
| 719 | mModeIndicatorView.setVisibility(View.GONE); |
| 720 | mRecordingIndicatorView.setVisibility(View.VISIBLE); |
| 721 | mRecordingTimeView.setText(""); |
| 722 | mRecordingTimeView.setVisibility(View.VISIBLE); |
| 723 | mHandler.sendEmptyMessage(UPDATE_RECORD_TIME); |
| 724 | } |
| 725 | } |
| 726 | |
| 727 | private void stopVideoRecordingAndDisplayDialog() { |
| 728 | Log.v(TAG, "stopVideoRecordingAndDisplayDialog"); |
| 729 | if (mMediaRecorderRecording) { |
| 730 | stopVideoRecording(); |
| 731 | acquireAndShowVideoFrame(); |
| 732 | showPostRecordingAlert(); |
| 733 | } |
| 734 | } |
| 735 | |
| 736 | private void showPostRecordingAlert() { |
| 737 | cancelRestartPreviewTimeout(); |
| 738 | mPostPictureAlert.setVisibility(View.VISIBLE); |
| 739 | mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, POST_PICTURE_ALERT_TIMEOUT); |
| 740 | } |
| 741 | |
| 742 | private void hidePostPictureAlert() { |
| 743 | cancelRestartPreviewTimeout(); |
| 744 | mPostPictureAlert.setVisibility(View.INVISIBLE); |
| 745 | } |
| 746 | |
| 747 | private void cancelRestartPreviewTimeout() { |
| 748 | mHandler.removeMessages(RESTART_PREVIEW); |
| 749 | } |
| 750 | |
| 751 | private boolean isPostRecordingAlertVisible() { |
| 752 | return mPostPictureAlert.getVisibility() == View.VISIBLE; |
| 753 | } |
| 754 | |
| 755 | private void stopVideoRecording() { |
| 756 | Log.v(TAG, "stopVideoRecording"); |
| 757 | if (mMediaRecorderRecording || mMediaRecorder != null) { |
| 758 | if (mMediaRecorderRecording) { |
| 759 | mMediaRecorder.stop(); |
| 760 | } |
| 761 | releaseMediaRecorder(); |
| 762 | mMediaRecorderRecording = false; |
| 763 | mModeIndicatorView.setVisibility(View.VISIBLE); |
| 764 | mRecordingIndicatorView.setVisibility(View.GONE); |
| 765 | mRecordingTimeView.setVisibility(View.GONE); |
| 766 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | private void hideVideoFrameAndStartPreview() { |
| 771 | hidePostPictureAlert(); |
| 772 | hideVideoFrame(); |
| 773 | restartPreview(); |
| 774 | } |
| 775 | |
| 776 | private void acquireAndShowVideoFrame() { |
| 777 | recycleVideoFrameBitmap(); |
| 778 | mVideoFrameBitmap = createVideoThumbnail(mCurrentVideoFilename); |
| 779 | mVideoFrame.setImageBitmap(mVideoFrameBitmap); |
| 780 | mVideoFrame.setVisibility(View.VISIBLE); |
| 781 | } |
| 782 | |
| 783 | private void hideVideoFrame() { |
| 784 | recycleVideoFrameBitmap(); |
| 785 | mVideoFrame.setVisibility(View.GONE); |
| 786 | } |
| 787 | |
| 788 | private void recycleVideoFrameBitmap() { |
| 789 | if (mVideoFrameBitmap != null) { |
| 790 | mVideoFrame.setImageDrawable(null); |
| 791 | mVideoFrameBitmap.recycle(); |
| 792 | mVideoFrameBitmap = null; |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | private Bitmap createVideoThumbnail(String filePath) { |
| 797 | Bitmap bitmap = null; |
| 798 | MediaMetadataRetriever retriever = new MediaMetadataRetriever(); |
| 799 | try { |
| 800 | retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); |
| 801 | retriever.setDataSource(filePath); |
| 802 | bitmap = retriever.captureFrame(); |
| 803 | } finally { |
| 804 | retriever.release(); |
| 805 | } |
| 806 | return bitmap; |
| 807 | } |
| 808 | |
| 809 | } |
| 810 | |