CameraActivity.java revision d1763a30cb0191e926467dc529e252b545ec9a9f
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.camera; 18 19import android.app.Activity; 20import android.content.BroadcastReceiver; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.ServiceConnection; 27import android.content.pm.ActivityInfo; 28import android.content.res.Configuration; 29import android.graphics.drawable.ColorDrawable; 30import android.net.Uri; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.IBinder; 34import android.provider.Settings; 35import android.util.Log; 36import android.view.KeyEvent; 37import android.view.LayoutInflater; 38import android.view.OrientationEventListener; 39import android.view.View; 40import android.view.ViewGroup; 41import android.view.Window; 42import android.view.WindowManager; 43import android.widget.ImageView; 44import android.widget.ProgressBar; 45 46import com.android.camera.data.CameraDataAdapter; 47import com.android.camera.data.CameraPreviewData; 48import com.android.camera.data.FixedFirstDataAdapter; 49import com.android.camera.data.FixedLastDataAdapter; 50import com.android.camera.data.LocalData; 51import com.android.camera.data.LocalDataAdapter; 52import com.android.camera.ui.CameraSwitcher; 53import com.android.camera.ui.CameraSwitcher.CameraSwitchListener; 54import com.android.camera.ui.FilmStripView; 55import com.android.gallery3d.R; 56import com.android.gallery3d.common.ApiHelper; 57import com.android.gallery3d.util.LightCycleHelper; 58import com.android.gallery3d.util.PanoramaViewHelper; 59import com.android.gallery3d.util.RefocusHelper; 60 61public class CameraActivity extends Activity 62 implements CameraSwitchListener { 63 64 private static final String TAG = "CAM_Activity"; 65 66 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 67 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 68 public static final String ACTION_IMAGE_CAPTURE_SECURE = 69 "android.media.action.IMAGE_CAPTURE_SECURE"; 70 71 // The intent extra for camera from secure lock screen. True if the gallery 72 // should only show newly captured pictures. sSecureAlbumId does not 73 // increment. This is used when switching between camera, camcorder, and 74 // panorama. If the extra is not set, it is in the normal camera mode. 75 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 76 77 /** This data adapter is used by FilmStirpView. */ 78 private LocalDataAdapter mDataAdapter; 79 /** This data adapter represents the real local camera data. */ 80 private LocalDataAdapter mWrappedDataAdapter; 81 82 private PanoramaStitchingManager mPanoramaManager; 83 private int mCurrentModuleIndex; 84 private CameraModule mCurrentModule; 85 private View mRootView; 86 private FilmStripView mFilmStripView; 87 private ProgressBar mBottomProgress; 88 private View mPanoStitchingPanel; 89 private int mResultCodeForTesting; 90 private Intent mResultDataForTesting; 91 private OnScreenHint mStorageHint; 92 private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD; 93 private boolean mAutoRotateScreen; 94 private boolean mSecureCamera; 95 // This is a hack to speed up the start of SecureCamera. 96 private static boolean sFirstStartAfterScreenOn = true; 97 private boolean mShowCameraPreview; 98 private int mLastRawOrientation; 99 private MyOrientationEventListener mOrientationListener; 100 private Handler mMainHandler; 101 private PanoramaViewHelper mPanoramaViewHelper; 102 private CameraPreviewData mCameraPreviewData; 103 104 private class MyOrientationEventListener 105 extends OrientationEventListener { 106 public MyOrientationEventListener(Context context) { 107 super(context); 108 } 109 110 @Override 111 public void onOrientationChanged(int orientation) { 112 // We keep the last known orientation. So if the user first orient 113 // the camera then point the camera to floor or sky, we still have 114 // the correct orientation. 115 if (orientation == ORIENTATION_UNKNOWN) return; 116 mLastRawOrientation = orientation; 117 mCurrentModule.onOrientationChanged(orientation); 118 } 119 } 120 121 private MediaSaveService mMediaSaveService; 122 private ServiceConnection mConnection = new ServiceConnection() { 123 @Override 124 public void onServiceConnected(ComponentName className, IBinder b) { 125 mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService(); 126 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 127 } 128 @Override 129 public void onServiceDisconnected(ComponentName className) { 130 mMediaSaveService = null; 131 }}; 132 133 // close activity when screen turns off 134 private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 135 @Override 136 public void onReceive(Context context, Intent intent) { 137 finish(); 138 } 139 }; 140 141 private static BroadcastReceiver sScreenOffReceiver; 142 private static class ScreenOffReceiver extends BroadcastReceiver { 143 @Override 144 public void onReceive(Context context, Intent intent) { 145 sFirstStartAfterScreenOn = true; 146 } 147 } 148 149 public static boolean isFirstStartAfterScreenOn() { 150 return sFirstStartAfterScreenOn; 151 } 152 153 public static void resetFirstStartAfterScreenOn() { 154 sFirstStartAfterScreenOn = false; 155 } 156 157 private FilmStripView.Listener mFilmStripListener = 158 new FilmStripView.Listener() { 159 @Override 160 public void onDataPromoted(int dataID) { 161 removeData(dataID); 162 } 163 164 @Override 165 public void onDataDemoted(int dataID) { 166 removeData(dataID); 167 } 168 169 @Override 170 public void onDataFullScreenChange(int dataID, boolean full) { 171 } 172 173 @Override 174 public void onSwitchMode(boolean toCamera) { 175 mCurrentModule.onSwitchMode(toCamera); 176 } 177 178 @Override 179 public void onCurrentDataChanged(int dataID, boolean current) { 180 if (!current) { 181 hidePanoStitchingProgress(); 182 } else { 183 LocalData currentData = mDataAdapter.getLocalData(dataID); 184 if (currentData == null) { 185 Log.w(TAG, "Current data ID not found."); 186 hidePanoStitchingProgress(); 187 return; 188 } 189 Uri contentUri = currentData.getContentUri(); 190 if (contentUri == null) { 191 hidePanoStitchingProgress(); 192 return; 193 } 194 int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri); 195 if (panoStitchingProgress < 0) { 196 hidePanoStitchingProgress(); 197 return; 198 } 199 showPanoStitchingProgress(); 200 updateStitchingProgress(panoStitchingProgress); 201 } 202 } 203 }; 204 205 private void hidePanoStitchingProgress() { 206 mPanoStitchingPanel.setVisibility(View.GONE); 207 } 208 209 private void showPanoStitchingProgress() { 210 mPanoStitchingPanel.setVisibility(View.VISIBLE); 211 } 212 213 private void updateStitchingProgress(int progress) { 214 mBottomProgress.setProgress(progress); 215 } 216 217 private Runnable mDeletionRunnable = new Runnable() { 218 @Override 219 public void run() { 220 mDataAdapter.executeDeletion(CameraActivity.this); 221 } 222 }; 223 224 private ImageTaskManager.TaskListener mStitchingListener = 225 new ImageTaskManager.TaskListener() { 226 @Override 227 public void onTaskQueued(String filePath, final Uri imageUri) { 228 mMainHandler.post(new Runnable() { 229 @Override 230 public void run() { 231 notifyNewMedia(imageUri); 232 } 233 }); 234 } 235 236 @Override 237 public void onTaskDone(String filePath, final Uri imageUri) { 238 Log.v(TAG, "onTaskDone:" + filePath); 239 mMainHandler.post(new Runnable() { 240 @Override 241 public void run() { 242 int doneID = mDataAdapter.findDataByContentUri(imageUri); 243 int currentDataId = mFilmStripView.getCurrentId(); 244 245 if (currentDataId == doneID) { 246 hidePanoStitchingProgress(); 247 updateStitchingProgress(0); 248 } 249 250 mDataAdapter.refresh(getContentResolver(), imageUri); 251 } 252 }); 253 } 254 255 @Override 256 public void onTaskProgress( 257 String filePath, final Uri imageUri, final int progress) { 258 mMainHandler.post(new Runnable() { 259 @Override 260 public void run() { 261 int currentDataId = mFilmStripView.getCurrentId(); 262 if (currentDataId == -1) { 263 return; 264 } 265 if (imageUri.equals( 266 mDataAdapter.getLocalData(currentDataId).getContentUri())) { 267 updateStitchingProgress(progress); 268 } 269 } 270 }); 271 } 272 }; 273 274 public MediaSaveService getMediaSaveService() { 275 return mMediaSaveService; 276 } 277 278 public void notifyNewMedia(Uri uri) { 279 ContentResolver cr = getContentResolver(); 280 String mimeType = cr.getType(uri); 281 if (mimeType.startsWith("video/")) { 282 sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO, uri)); 283 mDataAdapter.addNewVideo(cr, uri); 284 } else if (mimeType.startsWith("image/")) { 285 Util.broadcastNewPicture(this, uri); 286 mDataAdapter.addNewPhoto(cr, uri); 287 } else if (mimeType.startsWith("application/stitching-preview")) { 288 mDataAdapter.addNewPhoto(cr, uri); 289 } else { 290 android.util.Log.w(TAG, "Unknown new media with MIME type:" 291 + mimeType + ", uri:" + uri); 292 } 293 } 294 295 private void removeData(int dataID) { 296 mDataAdapter.removeData(CameraActivity.this, dataID); 297 mMainHandler.removeCallbacks(mDeletionRunnable); 298 mMainHandler.postDelayed(mDeletionRunnable, 3000); 299 } 300 301 private void bindMediaSaveService() { 302 Intent intent = new Intent(this, MediaSaveService.class); 303 startService(intent); // start service before binding it so the 304 // service won't be killed if we unbind it. 305 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 306 } 307 308 private void unbindMediaSaveService() { 309 if (mMediaSaveService != null) { 310 mMediaSaveService.setListener(null); 311 } 312 if (mConnection != null) { 313 unbindService(mConnection); 314 } 315 } 316 317 @Override 318 public void onCreate(Bundle state) { 319 super.onCreate(state); 320 setContentView(R.layout.camera_filmstrip); 321 if (ApiHelper.HAS_ROTATION_ANIMATION) { 322 setRotationAnimation(); 323 } 324 // Check if this is in the secure camera mode. 325 Intent intent = getIntent(); 326 String action = intent.getAction(); 327 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 328 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 329 mSecureCamera = true; 330 } else { 331 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 332 } 333 334 if (mSecureCamera) { 335 // Change the window flags so that secure camera can show when locked 336 Window win = getWindow(); 337 WindowManager.LayoutParams params = win.getAttributes(); 338 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 339 win.setAttributes(params); 340 341 // Filter for screen off so that we can finish secure camera activity 342 // when screen is off. 343 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 344 registerReceiver(mScreenOffReceiver, filter); 345 // TODO: This static screen off event receiver is a workaround to the 346 // double onResume() invocation (onResume->onPause->onResume). We should 347 // find a better solution to this. 348 if (sScreenOffReceiver == null) { 349 sScreenOffReceiver = new ScreenOffReceiver(); 350 registerReceiver(sScreenOffReceiver, filter); 351 } 352 } 353 mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this); 354 mPanoramaManager.addTaskListener(mStitchingListener); 355 LayoutInflater inflater = getLayoutInflater(); 356 View rootLayout = inflater.inflate(R.layout.camera, null, false); 357 mRootView = rootLayout.findViewById(R.id.camera_app_root); 358 mPanoStitchingPanel = (View) findViewById(R.id.pano_stitching_progress_panel); 359 mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar); 360 mCameraPreviewData = new CameraPreviewData(rootLayout, 361 FilmStripView.ImageData.SIZE_FULL, 362 FilmStripView.ImageData.SIZE_FULL); 363 // Put a CameraPreviewData at the first position. 364 mWrappedDataAdapter = new FixedFirstDataAdapter( 365 new CameraDataAdapter(new ColorDrawable( 366 getResources().getColor(R.color.photo_placeholder))), 367 mCameraPreviewData); 368 mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view); 369 mFilmStripView.setViewGap( 370 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 371 mPanoramaViewHelper = new PanoramaViewHelper(this); 372 mPanoramaViewHelper.onCreate(); 373 mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper); 374 // Set up the camera preview first so the preview shows up ASAP. 375 mFilmStripView.setListener(mFilmStripListener); 376 mCurrentModule = new PhotoModule(); 377 mCurrentModule.init(this, mRootView); 378 mOrientationListener = new MyOrientationEventListener(this); 379 mMainHandler = new Handler(getMainLooper()); 380 bindMediaSaveService(); 381 382 if (!mSecureCamera) { 383 mDataAdapter = mWrappedDataAdapter; 384 mDataAdapter.requestLoad(getContentResolver()); 385 } else { 386 // Put a lock placeholder as the last image by setting its date to 0. 387 ImageView v = (ImageView) getLayoutInflater().inflate( 388 R.layout.secure_album_placeholder, null); 389 mDataAdapter = new FixedLastDataAdapter( 390 mWrappedDataAdapter, 391 new LocalData.LocalViewData( 392 v, 393 v.getDrawable().getIntrinsicWidth(), 394 v.getDrawable().getIntrinsicHeight(), 395 0, 0)); 396 // Flush out all the original data. 397 mDataAdapter.flush(); 398 } 399 mFilmStripView.setDataAdapter(mDataAdapter); 400 } 401 402 private void setRotationAnimation() { 403 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 404 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 405 Window win = getWindow(); 406 WindowManager.LayoutParams winParams = win.getAttributes(); 407 winParams.rotationAnimation = rotationAnimation; 408 win.setAttributes(winParams); 409 } 410 411 @Override 412 public void onUserInteraction() { 413 super.onUserInteraction(); 414 mCurrentModule.onUserInteraction(); 415 } 416 417 @Override 418 public void onPause() { 419 mOrientationListener.disable(); 420 mCurrentModule.onPauseBeforeSuper(); 421 super.onPause(); 422 mCurrentModule.onPauseAfterSuper(); 423 } 424 425 @Override 426 public void onResume() { 427 if (Settings.System.getInt(getContentResolver(), 428 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off 429 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 430 mAutoRotateScreen = false; 431 } else { 432 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 433 mAutoRotateScreen = true; 434 } 435 mOrientationListener.enable(); 436 mCurrentModule.onResumeBeforeSuper(); 437 super.onResume(); 438 mCurrentModule.onResumeAfterSuper(); 439 440 setSwipingEnabled(true); 441 } 442 443 @Override 444 public void onStart() { 445 super.onStart(); 446 447 mPanoramaViewHelper.onStart(); 448 } 449 450 @Override 451 protected void onStop() { 452 super.onStop(); 453 mPanoramaViewHelper.onStop(); 454 } 455 456 @Override 457 public void onDestroy() { 458 unbindMediaSaveService(); 459 if (mSecureCamera) unregisterReceiver(mScreenOffReceiver); 460 super.onDestroy(); 461 } 462 463 @Override 464 public void onConfigurationChanged(Configuration config) { 465 super.onConfigurationChanged(config); 466 mCurrentModule.onConfigurationChanged(config); 467 } 468 469 @Override 470 public boolean onKeyDown(int keyCode, KeyEvent event) { 471 if (mCurrentModule.onKeyDown(keyCode, event)) return true; 472 // Prevent software keyboard or voice search from showing up. 473 if (keyCode == KeyEvent.KEYCODE_SEARCH 474 || keyCode == KeyEvent.KEYCODE_MENU) { 475 if (event.isLongPress()) return true; 476 } 477 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) { 478 return true; 479 } 480 481 return super.onKeyDown(keyCode, event); 482 } 483 484 @Override 485 public boolean onKeyUp(int keyCode, KeyEvent event) { 486 if (mCurrentModule.onKeyUp(keyCode, event)) return true; 487 if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) { 488 return true; 489 } 490 return super.onKeyUp(keyCode, event); 491 } 492 493 public boolean isAutoRotateScreen() { 494 return mAutoRotateScreen; 495 } 496 497 protected void updateStorageSpace() { 498 mStorageSpace = Storage.getAvailableSpace(); 499 } 500 501 protected long getStorageSpace() { 502 return mStorageSpace; 503 } 504 505 protected void updateStorageSpaceAndHint() { 506 updateStorageSpace(); 507 updateStorageHint(mStorageSpace); 508 } 509 510 protected void updateStorageHint() { 511 updateStorageHint(mStorageSpace); 512 } 513 514 protected boolean updateStorageHintOnResume() { 515 return true; 516 } 517 518 protected void updateStorageHint(long storageSpace) { 519 String message = null; 520 if (storageSpace == Storage.UNAVAILABLE) { 521 message = getString(R.string.no_storage); 522 } else if (storageSpace == Storage.PREPARING) { 523 message = getString(R.string.preparing_sd); 524 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 525 message = getString(R.string.access_sd_fail); 526 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { 527 message = getString(R.string.spaceIsLow_content); 528 } 529 530 if (message != null) { 531 if (mStorageHint == null) { 532 mStorageHint = OnScreenHint.makeText(this, message); 533 } else { 534 mStorageHint.setText(message); 535 } 536 mStorageHint.show(); 537 } else if (mStorageHint != null) { 538 mStorageHint.cancel(); 539 mStorageHint = null; 540 } 541 } 542 543 protected void setResultEx(int resultCode) { 544 mResultCodeForTesting = resultCode; 545 setResult(resultCode); 546 } 547 548 protected void setResultEx(int resultCode, Intent data) { 549 mResultCodeForTesting = resultCode; 550 mResultDataForTesting = data; 551 setResult(resultCode, data); 552 } 553 554 public int getResultCode() { 555 return mResultCodeForTesting; 556 } 557 558 public Intent getResultData() { 559 return mResultDataForTesting; 560 } 561 562 public boolean isSecureCamera() { 563 return mSecureCamera; 564 } 565 566 @Override 567 public void onCameraSelected(int i) { 568 if (mCurrentModuleIndex == i) return; 569 570 CameraHolder.instance().keep(); 571 closeModule(mCurrentModule); 572 mCurrentModuleIndex = i; 573 switch (i) { 574 case CameraSwitcher.VIDEO_MODULE_INDEX: 575 mCurrentModule = new VideoModule(); 576 break; 577 case CameraSwitcher.PHOTO_MODULE_INDEX: 578 mCurrentModule = new PhotoModule(); 579 break; 580 case CameraSwitcher.LIGHTCYCLE_MODULE_INDEX: 581 mCurrentModule = LightCycleHelper.createPanoramaModule(); 582 break; 583 case CameraSwitcher.REFOCUS_MODULE_INDEX: 584 mCurrentModule = RefocusHelper.createRefocusModule(); 585 break; 586 default: 587 break; 588 } 589 590 openModule(mCurrentModule); 591 mCurrentModule.onOrientationChanged(mLastRawOrientation); 592 if (mMediaSaveService != null) { 593 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 594 } 595 } 596 597 private void openModule(CameraModule module) { 598 module.init(this, mRootView); 599 module.onResumeBeforeSuper(); 600 module.onResumeAfterSuper(); 601 } 602 603 private void closeModule(CameraModule module) { 604 module.onPauseBeforeSuper(); 605 module.onPauseAfterSuper(); 606 ((ViewGroup) mRootView).removeAllViews(); 607 } 608 609 @Override 610 public void onShowSwitcherPopup() { 611 } 612 613 public void setSwipingEnabled(boolean enable) { 614 mCameraPreviewData.lockPreview(!enable); 615 } 616 617 // Accessor methods for getting latency times used in performance testing 618 public long getAutoFocusTime() { 619 return (mCurrentModule instanceof PhotoModule) ? 620 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 621 } 622 623 public long getShutterLag() { 624 return (mCurrentModule instanceof PhotoModule) ? 625 ((PhotoModule) mCurrentModule).mShutterLag : -1; 626 } 627 628 public long getShutterToPictureDisplayedTime() { 629 return (mCurrentModule instanceof PhotoModule) ? 630 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 631 } 632 633 public long getPictureDisplayedToJpegCallbackTime() { 634 return (mCurrentModule instanceof PhotoModule) ? 635 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 636 } 637 638 public long getJpegCallbackFinishTime() { 639 return (mCurrentModule instanceof PhotoModule) ? 640 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 641 } 642 643 public long getCaptureStartTime() { 644 return (mCurrentModule instanceof PhotoModule) ? 645 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 646 } 647 648 public boolean isRecording() { 649 return (mCurrentModule instanceof VideoModule) ? 650 ((VideoModule) mCurrentModule).isRecording() : false; 651 } 652} 653