CameraActivity.java revision b7639c641f2ed5d0c60546349c3aa9e90c026878
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.ActionBar; 20import android.app.Activity; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.ServiceConnection; 28import android.content.SharedPreferences; 29import android.content.pm.ActivityInfo; 30import android.content.res.Configuration; 31import android.graphics.drawable.ColorDrawable; 32import android.net.Uri; 33import android.os.AsyncTask; 34import android.os.Bundle; 35import android.os.Handler; 36import android.os.IBinder; 37import android.preference.PreferenceManager; 38import android.provider.MediaStore; 39import android.provider.Settings; 40import android.util.Log; 41import android.view.KeyEvent; 42import android.view.LayoutInflater; 43import android.view.Menu; 44import android.view.MenuInflater; 45import android.view.MenuItem; 46import android.view.OrientationEventListener; 47import android.view.View; 48import android.view.ViewGroup; 49import android.view.Window; 50import android.view.WindowManager; 51import android.widget.FrameLayout; 52import android.widget.ImageView; 53import android.widget.ProgressBar; 54import android.widget.ShareActionProvider; 55 56import com.android.camera.app.AppManagerFactory; 57import com.android.camera.app.PanoramaStitchingManager; 58import com.android.camera.data.CameraDataAdapter; 59import com.android.camera.data.CameraPreviewData; 60import com.android.camera.data.FixedFirstDataAdapter; 61import com.android.camera.data.FixedLastDataAdapter; 62import com.android.camera.data.LocalData; 63import com.android.camera.data.LocalDataAdapter; 64import com.android.camera.data.MediaDetails; 65import com.android.camera.data.SimpleViewData; 66import com.android.camera.ui.ModuleSwitcher; 67import com.android.camera.ui.DetailsDialog; 68import com.android.camera.ui.FilmStripView; 69import com.android.camera.util.ApiHelper; 70import com.android.camera.util.CameraUtil; 71import com.android.camera.util.PhotoSphereHelper; 72import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 73import com.android.camera2.R; 74 75public class CameraActivity extends Activity 76 implements ModuleSwitcher.ModuleSwitchListener { 77 78 private static final String TAG = "CAM_Activity"; 79 80 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 81 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 82 public static final String ACTION_IMAGE_CAPTURE_SECURE = 83 "android.media.action.IMAGE_CAPTURE_SECURE"; 84 public static final String ACTION_TRIM_VIDEO = 85 "com.android.camera.action.TRIM"; 86 public static final String MEDIA_ITEM_PATH = "media-item-path"; 87 88 private static final String PREF_STARTUP_MODULE_INDEX = "camera.startup_module"; 89 90 // The intent extra for camera from secure lock screen. True if the gallery 91 // should only show newly captured pictures. sSecureAlbumId does not 92 // increment. This is used when switching between camera, camcorder, and 93 // panorama. If the extra is not set, it is in the normal camera mode. 94 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 95 96 /** 97 * Request code from an activity we started that indicated that we do not 98 * want to reset the view to the preview in onResume. 99 */ 100 public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142; 101 102 /** Request code for external image editor activities. */ 103 private static final int REQ_CODE_EDIT = 1; 104 105 106 /** Whether onResume should reset the view to the preview. */ 107 private boolean mResetToPreviewOnResume = true; 108 109 // Supported operations at FilmStripView. Different data has different 110 // set of supported operations. 111 private static final int SUPPORT_DELETE = 1 << 0; 112 private static final int SUPPORT_ROTATE = 1 << 1; 113 private static final int SUPPORT_INFO = 1 << 2; 114 private static final int SUPPORT_CROP = 1 << 3; 115 private static final int SUPPORT_SETAS = 1 << 4; 116 private static final int SUPPORT_EDIT = 1 << 5; 117 private static final int SUPPORT_TRIM = 1 << 6; 118 private static final int SUPPORT_SHARE = 1 << 7; 119 private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8; 120 private static final int SUPPORT_SHOW_ON_MAP = 1 << 9; 121 private static final int SUPPORT_ALL = 0xffffffff; 122 123 /** This data adapter is used by FilmStripView. */ 124 private LocalDataAdapter mDataAdapter; 125 /** This data adapter represents the real local camera data. */ 126 private LocalDataAdapter mWrappedDataAdapter; 127 128 private PanoramaStitchingManager mPanoramaManager; 129 private int mCurrentModuleIndex; 130 private CameraModule mCurrentModule; 131 private FrameLayout mAboveFilmstripControlLayout; 132 private View mCameraModuleRootView; 133 private FilmStripView mFilmStripView; 134 private ProgressBar mBottomProgress; 135 private View mPanoStitchingPanel; 136 private int mResultCodeForTesting; 137 private Intent mResultDataForTesting; 138 private OnScreenHint mStorageHint; 139 private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD; 140 private boolean mAutoRotateScreen; 141 private boolean mSecureCamera; 142 // This is a hack to speed up the start of SecureCamera. 143 private static boolean sFirstStartAfterScreenOn = true; 144 private int mLastRawOrientation; 145 private MyOrientationEventListener mOrientationListener; 146 private Handler mMainHandler; 147 private PanoramaViewHelper mPanoramaViewHelper; 148 private CameraPreviewData mCameraPreviewData; 149 private ActionBar mActionBar; 150 private Menu mActionBarMenu; 151 private ViewGroup mUndoDeletionBar; 152 153 private ShareActionProvider mStandardShareActionProvider; 154 private Intent mStandardShareIntent; 155 private ShareActionProvider mPanoramaShareActionProvider; 156 private Intent mPanoramaShareIntent; 157 158 private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 159 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 160 161 public void gotoGallery() { 162 mFilmStripView.getController().goToNextItem(); 163 } 164 165 private class MyOrientationEventListener 166 extends OrientationEventListener { 167 public MyOrientationEventListener(Context context) { 168 super(context); 169 } 170 171 @Override 172 public void onOrientationChanged(int orientation) { 173 // We keep the last known orientation. So if the user first orient 174 // the camera then point the camera to floor or sky, we still have 175 // the correct orientation. 176 if (orientation == ORIENTATION_UNKNOWN) return; 177 mLastRawOrientation = orientation; 178 mCurrentModule.onOrientationChanged(orientation); 179 } 180 } 181 182 private MediaSaveService mMediaSaveService; 183 private ServiceConnection mConnection = new ServiceConnection() { 184 @Override 185 public void onServiceConnected(ComponentName className, IBinder b) { 186 mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService(); 187 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 188 } 189 @Override 190 public void onServiceDisconnected(ComponentName className) { 191 if (mMediaSaveService != null) { 192 mMediaSaveService.setListener(null); 193 mMediaSaveService = null; 194 } 195 }}; 196 197 // close activity when screen turns off 198 private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 199 @Override 200 public void onReceive(Context context, Intent intent) { 201 finish(); 202 } 203 }; 204 205 private static BroadcastReceiver sScreenOffReceiver; 206 private static class ScreenOffReceiver extends BroadcastReceiver { 207 @Override 208 public void onReceive(Context context, Intent intent) { 209 sFirstStartAfterScreenOn = true; 210 } 211 } 212 213 public static boolean isFirstStartAfterScreenOn() { 214 return sFirstStartAfterScreenOn; 215 } 216 217 public static void resetFirstStartAfterScreenOn() { 218 sFirstStartAfterScreenOn = false; 219 } 220 221 private FilmStripView.Listener mFilmStripListener = 222 new FilmStripView.Listener() { 223 @Override 224 public void onDataPromoted(int dataID) { 225 removeData(dataID); 226 } 227 228 @Override 229 public void onDataDemoted(int dataID) { 230 removeData(dataID); 231 } 232 233 @Override 234 public void onDataFullScreenChange(int dataID, boolean full) { 235 } 236 237 @Override 238 public void onSwitchMode(boolean toCamera) { 239 mCurrentModule.onSwitchMode(toCamera); 240 if (toCamera) { 241 setActionBarVisibilityAndLightsOut(true); 242 } else { 243 setActionBarVisibilityAndLightsOut(false); 244 } 245 } 246 247 @Override 248 public void onCurrentDataChanged(final int dataID, final boolean current) { 249 runOnUiThread(new Runnable() { 250 @Override 251 public void run() { 252 if (!current) { 253 hidePanoStitchingProgress(); 254 } else { 255 LocalData currentData = mDataAdapter.getLocalData(dataID); 256 if (currentData == null) { 257 Log.w(TAG, "Current data ID not found."); 258 hidePanoStitchingProgress(); 259 return; 260 } 261 262 if (currentData.getLocalDataType() == 263 LocalData.LOCAL_CAMERA_PREVIEW) { 264 // Don't show the action bar in Camera preview. 265 setActionBarVisibilityAndLightsOut(true); 266 } else { 267 updateActionBarMenu(dataID); 268 } 269 270 Uri contentUri = currentData.getContentUri(); 271 if (contentUri == null) { 272 hidePanoStitchingProgress(); 273 return; 274 } 275 int panoStitchingProgress = mPanoramaManager.getTaskProgress( 276 contentUri); 277 if (panoStitchingProgress < 0) { 278 hidePanoStitchingProgress(); 279 return; 280 } 281 showPanoStitchingProgress(); 282 updateStitchingProgress(panoStitchingProgress); 283 } 284 } 285 }); 286 } 287 288 @Override 289 public boolean onToggleActionBarVisibility() { 290 if (mActionBar.isShowing()) { 291 setActionBarVisibilityAndLightsOut(true); 292 } else { 293 // In the preview, don't show the action bar if that is 294 // a capture intent. 295 if (!isCaptureIntent()) { 296 setActionBarVisibilityAndLightsOut(false); 297 } 298 } 299 return mActionBar.isShowing(); 300 } 301 }; 302 303 /** 304 * If enabled, this hides the action bar and switches the system UI to 305 * lights-out mode. 306 */ 307 private void setActionBarVisibilityAndLightsOut(boolean enabled) { 308 if (enabled) { 309 mActionBar.hide(); 310 } else { 311 mActionBar.show(); 312 } 313 int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (enabled ? View.SYSTEM_UI_FLAG_LOW_PROFILE 314 : View.SYSTEM_UI_FLAG_VISIBLE); 315 mAboveFilmstripControlLayout 316 .setSystemUiVisibility(visibility); 317 } 318 319 private void hidePanoStitchingProgress() { 320 mPanoStitchingPanel.setVisibility(View.GONE); 321 } 322 323 private void showPanoStitchingProgress() { 324 mPanoStitchingPanel.setVisibility(View.VISIBLE); 325 } 326 327 private void updateStitchingProgress(int progress) { 328 mBottomProgress.setProgress(progress); 329 } 330 331 private void setStandardShareIntent(Uri contentUri, String mimeType) { 332 if (mStandardShareIntent == null) { 333 mStandardShareIntent = new Intent(Intent.ACTION_SEND); 334 } 335 mStandardShareIntent.setType(mimeType); 336 mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 337 if (mStandardShareActionProvider != null) { 338 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 339 } 340 } 341 342 private void setPanoramaShareIntent(Uri contentUri) { 343 if (mPanoramaShareIntent == null) { 344 mPanoramaShareIntent = new Intent(Intent.ACTION_SEND); 345 } 346 mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg"); 347 mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 348 if (mPanoramaShareActionProvider != null) { 349 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 350 } 351 } 352 353 /** 354 * According to the data type, make the menu items for supported operations 355 * visible. 356 * @param dataID the data ID of the current item. 357 */ 358 private void updateActionBarMenu(int dataID) { 359 LocalData currentData = mDataAdapter.getLocalData(dataID); 360 int type = currentData.getLocalDataType(); 361 362 if (mActionBarMenu == null) { 363 return; 364 } 365 366 int supported = 0; 367 switch (type) { 368 case LocalData.LOCAL_IMAGE: 369 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 370 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 371 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 372 break; 373 case LocalData.LOCAL_VIDEO: 374 supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM 375 | SUPPORT_SHARE; 376 break; 377 case LocalData.LOCAL_PHOTO_SPHERE: 378 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 379 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 380 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 381 break; 382 case LocalData.LOCAL_360_PHOTO_SPHERE: 383 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 384 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 385 | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360 386 | SUPPORT_SHOW_ON_MAP; 387 break; 388 default: 389 break; 390 } 391 392 setMenuItemVisible(mActionBarMenu, R.id.action_delete, 393 (supported & SUPPORT_DELETE) != 0); 394 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw, 395 (supported & SUPPORT_ROTATE) != 0); 396 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw, 397 (supported & SUPPORT_ROTATE) != 0); 398 setMenuItemVisible(mActionBarMenu, R.id.action_details, 399 (supported & SUPPORT_INFO) != 0); 400 setMenuItemVisible(mActionBarMenu, R.id.action_crop, 401 (supported & SUPPORT_CROP) != 0); 402 setMenuItemVisible(mActionBarMenu, R.id.action_setas, 403 (supported & SUPPORT_SETAS) != 0); 404 setMenuItemVisible(mActionBarMenu, R.id.action_edit, 405 (supported & SUPPORT_EDIT) != 0); 406 setMenuItemVisible(mActionBarMenu, R.id.action_trim, 407 (supported & SUPPORT_TRIM) != 0); 408 409 boolean standardShare = (supported & SUPPORT_SHARE) != 0; 410 boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0; 411 setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare); 412 setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare); 413 414 if (panoramaShare) { 415 // For 360 PhotoSphere, relegate standard share to the overflow menu 416 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 417 if (item != null) { 418 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 419 item.setTitle(getResources().getString(R.string.share_as_photo)); 420 } 421 // And, promote "share as panorama" to action bar 422 item = mActionBarMenu.findItem(R.id.action_share_panorama); 423 if (item != null) { 424 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 425 } 426 setPanoramaShareIntent(currentData.getContentUri()); 427 } 428 if (standardShare) { 429 if (!panoramaShare) { 430 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 431 if (item != null) { 432 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 433 item.setTitle(getResources().getString(R.string.share)); 434 } 435 } 436 setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType()); 437 } 438 439 boolean itemHasLocation = currentData.getLatLong() != null; 440 setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map, 441 itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0); 442 } 443 444 private void setMenuItemVisible(Menu menu, int itemId, boolean visible) { 445 MenuItem item = menu.findItem(itemId); 446 if (item != null) 447 item.setVisible(visible); 448 } 449 450 private Runnable mDeletionRunnable = new Runnable() { 451 @Override 452 public void run() { 453 hideUndoDeletionBar(); 454 mDataAdapter.executeDeletion(CameraActivity.this); 455 } 456 }; 457 458 private ImageTaskManager.TaskListener mStitchingListener = 459 new ImageTaskManager.TaskListener() { 460 @Override 461 public void onTaskQueued(String filePath, final Uri imageUri) { 462 mMainHandler.post(new Runnable() { 463 @Override 464 public void run() { 465 notifyNewMedia(imageUri); 466 } 467 }); 468 } 469 470 @Override 471 public void onTaskDone(String filePath, final Uri imageUri) { 472 Log.v(TAG, "onTaskDone:" + filePath); 473 mMainHandler.post(new Runnable() { 474 @Override 475 public void run() { 476 int doneID = mDataAdapter.findDataByContentUri(imageUri); 477 int currentDataId = mFilmStripView.getCurrentId(); 478 479 if (currentDataId == doneID) { 480 hidePanoStitchingProgress(); 481 updateStitchingProgress(0); 482 } 483 484 mDataAdapter.refresh(getContentResolver(), imageUri); 485 } 486 }); 487 } 488 489 @Override 490 public void onTaskProgress( 491 String filePath, final Uri imageUri, final int progress) { 492 mMainHandler.post(new Runnable() { 493 @Override 494 public void run() { 495 int currentDataId = mFilmStripView.getCurrentId(); 496 if (currentDataId == -1) { 497 return; 498 } 499 if (imageUri.equals( 500 mDataAdapter.getLocalData(currentDataId).getContentUri())) { 501 updateStitchingProgress(progress); 502 } 503 } 504 }); 505 } 506 }; 507 508 public MediaSaveService getMediaSaveService() { 509 return mMediaSaveService; 510 } 511 512 public void notifyNewMedia(Uri uri) { 513 ContentResolver cr = getContentResolver(); 514 String mimeType = cr.getType(uri); 515 if (mimeType.startsWith("video/")) { 516 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 517 mDataAdapter.addNewVideo(cr, uri); 518 } else if (mimeType.startsWith("image/")) { 519 CameraUtil.broadcastNewPicture(this, uri); 520 mDataAdapter.addNewPhoto(cr, uri); 521 } else if (mimeType.startsWith("application/stitching-preview")) { 522 mDataAdapter.addNewPhoto(cr, uri); 523 } else { 524 android.util.Log.w(TAG, "Unknown new media with MIME type:" 525 + mimeType + ", uri:" + uri); 526 } 527 } 528 529 private void removeData(int dataID) { 530 mDataAdapter.removeData(CameraActivity.this, dataID); 531 showUndoDeletionBar(); 532 mMainHandler.removeCallbacks(mDeletionRunnable); 533 mMainHandler.postDelayed(mDeletionRunnable, 3000); 534 } 535 536 private void bindMediaSaveService() { 537 Intent intent = new Intent(this, MediaSaveService.class); 538 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 539 } 540 541 private void unbindMediaSaveService() { 542 if (mConnection != null) { 543 unbindService(mConnection); 544 } 545 } 546 547 @Override 548 public boolean onCreateOptionsMenu(Menu menu) { 549 // Inflate the menu items for use in the action bar 550 MenuInflater inflater = getMenuInflater(); 551 inflater.inflate(R.menu.operations, menu); 552 mActionBarMenu = menu; 553 554 // Configure the standard share action provider 555 MenuItem item = menu.findItem(R.id.action_share); 556 mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider(); 557 mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml"); 558 if (mStandardShareIntent != null) { 559 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 560 } 561 562 // Configure the panorama share action provider 563 item = menu.findItem(R.id.action_share_panorama); 564 mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider(); 565 mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml"); 566 if (mPanoramaShareIntent != null) { 567 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 568 } 569 570 return super.onCreateOptionsMenu(menu); 571 } 572 573 @Override 574 public boolean onOptionsItemSelected(MenuItem item) { 575 int currentDataId = mFilmStripView.getCurrentId(); 576 if (currentDataId < 0) { 577 return false; 578 } 579 final LocalData localData = mDataAdapter.getLocalData(currentDataId); 580 581 // Handle presses on the action bar items 582 switch (item.getItemId()) { 583 case android.R.id.home: 584 // ActionBar's Up/Home button was clicked 585 mFilmStripView.getController().goToFirstItem(); 586 return true; 587 case R.id.action_delete: 588 removeData(currentDataId); 589 return true; 590 case R.id.action_edit: 591 launchEditor(localData); 592 return true; 593 case R.id.action_trim: { 594 // This is going to be handled by the Gallery app. 595 Intent intent = new Intent(ACTION_TRIM_VIDEO); 596 LocalData currentData = mDataAdapter.getLocalData( 597 mFilmStripView.getCurrentId()); 598 intent.setData(currentData.getContentUri()); 599 // We need the file path to wrap this into a RandomAccessFile. 600 intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath()); 601 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 602 return true; 603 } 604 case R.id.action_rotate_ccw: 605 localData.rotate90Degrees(this, mDataAdapter, currentDataId, false); 606 return true; 607 case R.id.action_rotate_cw: 608 localData.rotate90Degrees(this, mDataAdapter, currentDataId, true); 609 return true; 610 case R.id.action_crop: 611 // TODO: add the functionality. 612 return true; 613 case R.id.action_setas: { 614 Intent intent = new Intent(Intent.ACTION_ATTACH_DATA) 615 .setDataAndType(localData.getContentUri(), 616 localData.getMimeType()) 617 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 618 intent.putExtra("mimeType", intent.getType()); 619 startActivityForResult(Intent.createChooser( 620 intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW); 621 return true; 622 } 623 case R.id.action_details: 624 (new AsyncTask<Void, Void, MediaDetails>() { 625 @Override 626 protected MediaDetails doInBackground(Void... params) { 627 return localData.getMediaDetails(CameraActivity.this); 628 } 629 630 @Override 631 protected void onPostExecute(MediaDetails mediaDetails) { 632 DetailsDialog.create(CameraActivity.this, mediaDetails).show(); 633 } 634 }).execute(); 635 return true; 636 case R.id.action_show_on_map: 637 double[] latLong = localData.getLatLong(); 638 if (latLong != null) { 639 CameraUtil.showOnMap(this, latLong); 640 } 641 return true; 642 default: 643 return super.onOptionsItemSelected(item); 644 } 645 } 646 647 private boolean isCaptureIntent() { 648 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 649 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 650 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 651 return true; 652 } else { 653 return false; 654 } 655 } 656 657 @Override 658 public void onCreate(Bundle state) { 659 super.onCreate(state); 660 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 661 setContentView(R.layout.camera_filmstrip); 662 mActionBar = getActionBar(); 663 664 if (ApiHelper.HAS_ROTATION_ANIMATION) { 665 setRotationAnimation(); 666 } 667 // Check if this is in the secure camera mode. 668 Intent intent = getIntent(); 669 String action = intent.getAction(); 670 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 671 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 672 mSecureCamera = true; 673 } else { 674 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 675 } 676 677 if (mSecureCamera) { 678 // Change the window flags so that secure camera can show when locked 679 Window win = getWindow(); 680 WindowManager.LayoutParams params = win.getAttributes(); 681 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 682 win.setAttributes(params); 683 684 // Filter for screen off so that we can finish secure camera activity 685 // when screen is off. 686 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 687 registerReceiver(mScreenOffReceiver, filter); 688 // TODO: This static screen off event receiver is a workaround to the 689 // double onResume() invocation (onResume->onPause->onResume). We should 690 // find a better solution to this. 691 if (sScreenOffReceiver == null) { 692 sScreenOffReceiver = new ScreenOffReceiver(); 693 registerReceiver(sScreenOffReceiver, filter); 694 } 695 } 696 mAboveFilmstripControlLayout = 697 (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout); 698 mAboveFilmstripControlLayout.setFitsSystemWindows(true); 699 // Hide action bar first since we are in full screen mode first, and 700 // switch the system UI to lights-out mode. 701 setActionBarVisibilityAndLightsOut(true); 702 mPanoramaManager = AppManagerFactory.getInstance(this) 703 .getPanoramaStitchingManager(); 704 mPanoramaManager.addTaskListener(mStitchingListener); 705 LayoutInflater inflater = getLayoutInflater(); 706 View rootLayout = inflater.inflate(R.layout.camera, null, false); 707 mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root); 708 mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel); 709 mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar); 710 mCameraPreviewData = new CameraPreviewData(rootLayout, 711 FilmStripView.ImageData.SIZE_FULL, 712 FilmStripView.ImageData.SIZE_FULL); 713 // Put a CameraPreviewData at the first position. 714 mWrappedDataAdapter = new FixedFirstDataAdapter( 715 new CameraDataAdapter(new ColorDrawable( 716 getResources().getColor(R.color.photo_placeholder))), 717 mCameraPreviewData); 718 mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view); 719 mFilmStripView.setViewGap( 720 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 721 mPanoramaViewHelper = new PanoramaViewHelper(this); 722 mPanoramaViewHelper.onCreate(); 723 mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper); 724 // Set up the camera preview first so the preview shows up ASAP. 725 mFilmStripView.setListener(mFilmStripListener); 726 727 int moduleIndex = -1; 728 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) 729 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { 730 moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX; 731 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 732 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 733 .getAction()) 734 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 735 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 736 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 737 } else { 738 // If the activity has not been started using an explicit intent, 739 // read the module index from the last time the user changed modes 740 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 741 moduleIndex = prefs.getInt(PREF_STARTUP_MODULE_INDEX, -1); 742 if (moduleIndex < 0) { 743 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 744 } 745 } 746 747 mOrientationListener = new MyOrientationEventListener(this); 748 setModuleFromIndex(moduleIndex); 749 mCurrentModule.init(this, mCameraModuleRootView); 750 mMainHandler = new Handler(getMainLooper()); 751 752 if (!mSecureCamera) { 753 mDataAdapter = mWrappedDataAdapter; 754 mFilmStripView.setDataAdapter(mDataAdapter); 755 if (!isCaptureIntent()) { 756 mDataAdapter.requestLoad(getContentResolver()); 757 } 758 } else { 759 // Put a lock placeholder as the last image by setting its date to 0. 760 ImageView v = (ImageView) getLayoutInflater().inflate( 761 R.layout.secure_album_placeholder, null); 762 mDataAdapter = new FixedLastDataAdapter( 763 mWrappedDataAdapter, 764 new SimpleViewData( 765 v, 766 v.getDrawable().getIntrinsicWidth(), 767 v.getDrawable().getIntrinsicHeight(), 768 0, 0)); 769 // Flush out all the original data. 770 mDataAdapter.flush(); 771 mFilmStripView.setDataAdapter(mDataAdapter); 772 } 773 } 774 775 private void setRotationAnimation() { 776 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 777 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 778 Window win = getWindow(); 779 WindowManager.LayoutParams winParams = win.getAttributes(); 780 winParams.rotationAnimation = rotationAnimation; 781 win.setAttributes(winParams); 782 } 783 784 @Override 785 public void onUserInteraction() { 786 super.onUserInteraction(); 787 mCurrentModule.onUserInteraction(); 788 } 789 790 @Override 791 public void onPause() { 792 mOrientationListener.disable(); 793 mCurrentModule.onPauseBeforeSuper(); 794 super.onPause(); 795 mCurrentModule.onPauseAfterSuper(); 796 } 797 798 @Override 799 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 800 if (requestCode == REQ_CODE_EDIT && resultCode == RESULT_OK) { 801 Uri uri = data.getData(); 802 ContentResolver contentResolver = getContentResolver(); 803 if (uri == null) { 804 // If we don't have a particular uri returned, then we have 805 // to refresh all, it is not optimal, but works best so far. 806 // Also don't requestLoad() when in secure camera mode. 807 if (!mSecureCamera) { 808 mDataAdapter.requestLoad(contentResolver); 809 } 810 } else { 811 mDataAdapter.refresh(contentResolver, uri); 812 } 813 } 814 815 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW | requestCode == REQ_CODE_EDIT) { 816 mResetToPreviewOnResume = false; 817 } else { 818 super.onActivityResult(requestCode, resultCode, data); 819 } 820 } 821 822 @Override 823 public void onResume() { 824 // TODO: Handle this in OrientationManager. 825 if (Settings.System.getInt(getContentResolver(), 826 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off 827 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 828 mAutoRotateScreen = false; 829 } else { 830 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 831 mAutoRotateScreen = true; 832 } 833 mOrientationListener.enable(); 834 mCurrentModule.onResumeBeforeSuper(); 835 super.onResume(); 836 mCurrentModule.onResumeAfterSuper(); 837 838 setSwipingEnabled(true); 839 840 if (mResetToPreviewOnResume) { 841 // Go to the preview on resume. 842 mFilmStripView.getController().goToFirstItem(); 843 } 844 // Default is showing the preview, unless disabled by explicitly 845 // starting an activity we want to return from to the filmstrip rather 846 // than the preview. 847 mResetToPreviewOnResume = true; 848 } 849 850 @Override 851 public void onStart() { 852 super.onStart(); 853 bindMediaSaveService(); 854 mPanoramaViewHelper.onStart(); 855 } 856 857 @Override 858 protected void onStop() { 859 super.onStop(); 860 mPanoramaViewHelper.onStop(); 861 unbindMediaSaveService(); 862 } 863 864 @Override 865 public void onDestroy() { 866 if (mSecureCamera) unregisterReceiver(mScreenOffReceiver); 867 super.onDestroy(); 868 } 869 870 @Override 871 public void onConfigurationChanged(Configuration config) { 872 super.onConfigurationChanged(config); 873 mCurrentModule.onConfigurationChanged(config); 874 } 875 876 @Override 877 public boolean onKeyDown(int keyCode, KeyEvent event) { 878 if (mCurrentModule.onKeyDown(keyCode, event)) return true; 879 // Prevent software keyboard or voice search from showing up. 880 if (keyCode == KeyEvent.KEYCODE_SEARCH 881 || keyCode == KeyEvent.KEYCODE_MENU) { 882 if (event.isLongPress()) return true; 883 } 884 885 return super.onKeyDown(keyCode, event); 886 } 887 888 @Override 889 public boolean onKeyUp(int keyCode, KeyEvent event) { 890 if (mCurrentModule.onKeyUp(keyCode, event)) return true; 891 return super.onKeyUp(keyCode, event); 892 } 893 894 @Override 895 public void onBackPressed() { 896 if (!mFilmStripView.inCameraFullscreen()) { 897 mFilmStripView.getController().goToFirstItem(); 898 } else if (!mCurrentModule.onBackPressed()) { 899 super.onBackPressed(); 900 } 901 } 902 903 public boolean isAutoRotateScreen() { 904 return mAutoRotateScreen; 905 } 906 907 protected void updateStorageSpace() { 908 mStorageSpace = Storage.getAvailableSpace(); 909 } 910 911 protected long getStorageSpace() { 912 return mStorageSpace; 913 } 914 915 protected void updateStorageSpaceAndHint() { 916 updateStorageSpace(); 917 updateStorageHint(mStorageSpace); 918 } 919 920 protected void updateStorageHint() { 921 updateStorageHint(mStorageSpace); 922 } 923 924 protected boolean updateStorageHintOnResume() { 925 return true; 926 } 927 928 protected void updateStorageHint(long storageSpace) { 929 String message = null; 930 if (storageSpace == Storage.UNAVAILABLE) { 931 message = getString(R.string.no_storage); 932 } else if (storageSpace == Storage.PREPARING) { 933 message = getString(R.string.preparing_sd); 934 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 935 message = getString(R.string.access_sd_fail); 936 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { 937 message = getString(R.string.spaceIsLow_content); 938 } 939 940 if (message != null) { 941 if (mStorageHint == null) { 942 mStorageHint = OnScreenHint.makeText(this, message); 943 } else { 944 mStorageHint.setText(message); 945 } 946 mStorageHint.show(); 947 } else if (mStorageHint != null) { 948 mStorageHint.cancel(); 949 mStorageHint = null; 950 } 951 } 952 953 protected void setResultEx(int resultCode) { 954 mResultCodeForTesting = resultCode; 955 setResult(resultCode); 956 } 957 958 protected void setResultEx(int resultCode, Intent data) { 959 mResultCodeForTesting = resultCode; 960 mResultDataForTesting = data; 961 setResult(resultCode, data); 962 } 963 964 public int getResultCode() { 965 return mResultCodeForTesting; 966 } 967 968 public Intent getResultData() { 969 return mResultDataForTesting; 970 } 971 972 public boolean isSecureCamera() { 973 return mSecureCamera; 974 } 975 976 @Override 977 public void onModuleSelected(int moduleIndex) { 978 if (mCurrentModuleIndex == moduleIndex) return; 979 980 CameraHolder.instance().keep(); 981 closeModule(mCurrentModule); 982 setModuleFromIndex(moduleIndex); 983 984 openModule(mCurrentModule); 985 mCurrentModule.onOrientationChanged(mLastRawOrientation); 986 if (mMediaSaveService != null) { 987 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 988 } 989 990 // Store the module index so we can use it the next time the Camera 991 // starts up. 992 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 993 prefs.edit().putInt(PREF_STARTUP_MODULE_INDEX, moduleIndex).apply(); 994 } 995 996 /** 997 * Sets the mCurrentModuleIndex, creates a new module instance for the 998 * given index an sets it as mCurrentModule. 999 */ 1000 private void setModuleFromIndex(int moduleIndex) { 1001 mCurrentModuleIndex = moduleIndex; 1002 switch (moduleIndex) { 1003 case ModuleSwitcher.VIDEO_MODULE_INDEX: { 1004 mCurrentModule = new VideoModule(); 1005 break; 1006 } 1007 1008 case ModuleSwitcher.PHOTO_MODULE_INDEX: { 1009 mCurrentModule = new PhotoModule(); 1010 break; 1011 } 1012 1013 case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX: { 1014 mCurrentModule = new WideAnglePanoramaModule(); 1015 break; 1016 } 1017 1018 case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: { 1019 mCurrentModule = PhotoSphereHelper.createPanoramaModule(); 1020 break; 1021 } 1022 1023 default: 1024 break; 1025 } 1026 } 1027 1028 /** 1029 * Launches an ACTION_EDIT intent for the given local data item. 1030 */ 1031 public void launchEditor(LocalData data) { 1032 Intent intent = new Intent(Intent.ACTION_EDIT) 1033 .setDataAndType(data.getContentUri(), data.getMimeType()) 1034 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1035 startActivityForResult(Intent.createChooser(intent, null), REQ_CODE_EDIT); 1036 } 1037 1038 private void openModule(CameraModule module) { 1039 module.init(this, mCameraModuleRootView); 1040 module.onResumeBeforeSuper(); 1041 module.onResumeAfterSuper(); 1042 } 1043 1044 private void closeModule(CameraModule module) { 1045 module.onPauseBeforeSuper(); 1046 module.onPauseAfterSuper(); 1047 ((ViewGroup) mCameraModuleRootView).removeAllViews(); 1048 } 1049 1050 private void showUndoDeletionBar() { 1051 if (mUndoDeletionBar == null) { 1052 Log.v(TAG, "showing undo bar"); 1053 ViewGroup v = (ViewGroup) getLayoutInflater().inflate( 1054 R.layout.undo_bar, mAboveFilmstripControlLayout, true); 1055 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 1056 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 1057 button.setOnClickListener(new View.OnClickListener() { 1058 @Override 1059 public void onClick(View view) { 1060 mDataAdapter.undoDataRemoval(); 1061 mMainHandler.removeCallbacks(mDeletionRunnable); 1062 hideUndoDeletionBar(); 1063 } 1064 }); 1065 } 1066 mUndoDeletionBar.setAlpha(0f); 1067 mUndoDeletionBar.setVisibility(View.VISIBLE); 1068 mUndoDeletionBar.animate().setDuration(200).alpha(1f).start(); 1069 } 1070 1071 private void hideUndoDeletionBar() { 1072 Log.v(TAG, "Hiding undo deletion bar"); 1073 if (mUndoDeletionBar != null) { 1074 mUndoDeletionBar.animate() 1075 .setDuration(200) 1076 .alpha(0f) 1077 .withEndAction(new Runnable() { 1078 @Override 1079 public void run() { 1080 mUndoDeletionBar.setVisibility(View.GONE); 1081 } 1082 }) 1083 .start(); 1084 } 1085 } 1086 1087 @Override 1088 public void onShowSwitcherPopup() { 1089 } 1090 1091 /** 1092 * Enable/disable swipe-to-filmstrip. 1093 * Will always disable swipe if in capture intent. 1094 * 1095 * @param enable {@code true} to enable swipe. 1096 */ 1097 public void setSwipingEnabled(boolean enable) { 1098 if (isCaptureIntent()) { 1099 mCameraPreviewData.lockPreview(true); 1100 } else { 1101 mCameraPreviewData.lockPreview(!enable); 1102 } 1103 } 1104 1105 // Accessor methods for getting latency times used in performance testing 1106 public long getAutoFocusTime() { 1107 return (mCurrentModule instanceof PhotoModule) ? 1108 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 1109 } 1110 1111 public long getShutterLag() { 1112 return (mCurrentModule instanceof PhotoModule) ? 1113 ((PhotoModule) mCurrentModule).mShutterLag : -1; 1114 } 1115 1116 public long getShutterToPictureDisplayedTime() { 1117 return (mCurrentModule instanceof PhotoModule) ? 1118 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 1119 } 1120 1121 public long getPictureDisplayedToJpegCallbackTime() { 1122 return (mCurrentModule instanceof PhotoModule) ? 1123 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 1124 } 1125 1126 public long getJpegCallbackFinishTime() { 1127 return (mCurrentModule instanceof PhotoModule) ? 1128 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 1129 } 1130 1131 public long getCaptureStartTime() { 1132 return (mCurrentModule instanceof PhotoModule) ? 1133 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 1134 } 1135 1136 public boolean isRecording() { 1137 return (mCurrentModule instanceof VideoModule) ? 1138 ((VideoModule) mCurrentModule).isRecording() : false; 1139 } 1140} 1141