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 17 package com.android.camera; 18 19 import android.animation.Animator; 20 import android.annotation.TargetApi; 21 import android.app.ActionBar; 22 import android.app.Activity; 23 import android.content.ActivityNotFoundException; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.ServiceConnection; 31 import android.content.SharedPreferences; 32 import android.content.pm.ActivityInfo; 33 import android.content.res.Configuration; 34 import android.graphics.drawable.ColorDrawable; 35 import android.net.Uri; 36 import android.nfc.NfcAdapter; 37 import android.nfc.NfcAdapter.CreateBeamUrisCallback; 38 import android.nfc.NfcEvent; 39 import android.os.AsyncTask; 40 import android.os.Build; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.IBinder; 44 import android.os.Looper; 45 import android.os.Message; 46 import android.preference.PreferenceManager; 47 import android.provider.MediaStore; 48 import android.provider.Settings; 49 import android.util.Log; 50 import android.view.KeyEvent; 51 import android.view.LayoutInflater; 52 import android.view.Menu; 53 import android.view.MenuInflater; 54 import android.view.MenuItem; 55 import android.view.MotionEvent; 56 import android.view.OrientationEventListener; 57 import android.view.View; 58 import android.view.ViewGroup; 59 import android.view.Window; 60 import android.view.WindowManager; 61 import android.widget.FrameLayout; 62 import android.widget.ImageView; 63 import android.widget.ProgressBar; 64 import android.widget.ShareActionProvider; 65 66 import com.android.camera.app.AppManagerFactory; 67 import com.android.camera.app.PanoramaStitchingManager; 68 import com.android.camera.crop.CropActivity; 69 import com.android.camera.data.CameraDataAdapter; 70 import com.android.camera.data.CameraPreviewData; 71 import com.android.camera.data.FixedFirstDataAdapter; 72 import com.android.camera.data.FixedLastDataAdapter; 73 import com.android.camera.data.InProgressDataWrapper; 74 import com.android.camera.data.LocalData; 75 import com.android.camera.data.LocalDataAdapter; 76 import com.android.camera.data.LocalMediaObserver; 77 import com.android.camera.data.MediaDetails; 78 import com.android.camera.data.SimpleViewData; 79 import com.android.camera.tinyplanet.TinyPlanetFragment; 80 import com.android.camera.ui.ModuleSwitcher; 81 import com.android.camera.ui.DetailsDialog; 82 import com.android.camera.ui.FilmStripView; 83 import com.android.camera.util.ApiHelper; 84 import com.android.camera.util.CameraUtil; 85 import com.android.camera.util.GcamHelper; 86 import com.android.camera.util.PhotoSphereHelper; 87 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 88 import com.android.camera2.R; 89 90 import static com.android.camera.CameraManager.CameraOpenErrorCallback; 91 92 public class CameraActivity extends Activity 93 implements ModuleSwitcher.ModuleSwitchListener, 94 ActionBar.OnMenuVisibilityListener { 95 96 private static final String TAG = "CAM_Activity"; 97 98 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 99 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 100 public static final String ACTION_IMAGE_CAPTURE_SECURE = 101 "android.media.action.IMAGE_CAPTURE_SECURE"; 102 public static final String ACTION_TRIM_VIDEO = 103 "com.android.camera.action.TRIM"; 104 public static final String MEDIA_ITEM_PATH = "media-item-path"; 105 106 // The intent extra for camera from secure lock screen. True if the gallery 107 // should only show newly captured pictures. sSecureAlbumId does not 108 // increment. This is used when switching between camera, camcorder, and 109 // panorama. If the extra is not set, it is in the normal camera mode. 110 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 111 112 /** 113 * Request code from an activity we started that indicated that we do not 114 * want to reset the view to the preview in onResume. 115 */ 116 public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142; 117 118 public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999; 119 120 private static final int HIDE_ACTION_BAR = 1; 121 private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000; 122 123 /** Whether onResume should reset the view to the preview. */ 124 private boolean mResetToPreviewOnResume = true; 125 126 // Supported operations at FilmStripView. Different data has different 127 // set of supported operations. 128 private static final int SUPPORT_DELETE = 1 << 0; 129 private static final int SUPPORT_ROTATE = 1 << 1; 130 private static final int SUPPORT_INFO = 1 << 2; 131 private static final int SUPPORT_CROP = 1 << 3; 132 private static final int SUPPORT_SETAS = 1 << 4; 133 private static final int SUPPORT_EDIT = 1 << 5; 134 private static final int SUPPORT_TRIM = 1 << 6; 135 private static final int SUPPORT_SHARE = 1 << 7; 136 private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8; 137 private static final int SUPPORT_SHOW_ON_MAP = 1 << 9; 138 private static final int SUPPORT_ALL = 0xffffffff; 139 140 /** This data adapter is used by FilmStripView. */ 141 private LocalDataAdapter mDataAdapter; 142 /** This data adapter represents the real local camera data. */ 143 private LocalDataAdapter mWrappedDataAdapter; 144 145 private PanoramaStitchingManager mPanoramaManager; 146 private int mCurrentModuleIndex; 147 private CameraModule mCurrentModule; 148 private FrameLayout mAboveFilmstripControlLayout; 149 private View mCameraModuleRootView; 150 private FilmStripView mFilmStripView; 151 private ProgressBar mBottomProgress; 152 private View mPanoStitchingPanel; 153 private int mResultCodeForTesting; 154 private Intent mResultDataForTesting; 155 private OnScreenHint mStorageHint; 156 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES; 157 private boolean mAutoRotateScreen; 158 private boolean mSecureCamera; 159 // This is a hack to speed up the start of SecureCamera. 160 private static boolean sFirstStartAfterScreenOn = true; 161 private int mLastRawOrientation; 162 private MyOrientationEventListener mOrientationListener; 163 private Handler mMainHandler; 164 private PanoramaViewHelper mPanoramaViewHelper; 165 private CameraPreviewData mCameraPreviewData; 166 private ActionBar mActionBar; 167 private OnActionBarVisibilityListener mOnActionBarVisibilityListener = null; 168 private Menu mActionBarMenu; 169 private ViewGroup mUndoDeletionBar; 170 private boolean mIsUndoingDeletion = false; 171 172 private Uri[] mNfcPushUris = new Uri[1]; 173 174 private ShareActionProvider mStandardShareActionProvider; 175 private Intent mStandardShareIntent; 176 private ShareActionProvider mPanoramaShareActionProvider; 177 private Intent mPanoramaShareIntent; 178 private LocalMediaObserver mLocalImagesObserver; 179 private LocalMediaObserver mLocalVideosObserver; 180 181 private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 182 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 183 private boolean mPendingDeletion = false; 184 185 private Intent mVideoShareIntent; 186 private Intent mImageShareIntent; 187 188 private class MyOrientationEventListener 189 extends OrientationEventListener { 190 public MyOrientationEventListener(Context context) { 191 super(context); 192 } 193 194 @Override 195 public void onOrientationChanged(int orientation) { 196 // We keep the last known orientation. So if the user first orient 197 // the camera then point the camera to floor or sky, we still have 198 // the correct orientation. 199 if (orientation == ORIENTATION_UNKNOWN) { 200 return; 201 } 202 mLastRawOrientation = orientation; 203 mCurrentModule.onOrientationChanged(orientation); 204 } 205 } 206 207 private MediaSaveService mMediaSaveService; 208 private ServiceConnection mConnection = new ServiceConnection() { 209 @Override 210 public void onServiceConnected(ComponentName className, IBinder b) { 211 mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService(); 212 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 213 } 214 215 @Override 216 public void onServiceDisconnected(ComponentName className) { 217 if (mMediaSaveService != null) { 218 mMediaSaveService.setListener(null); 219 mMediaSaveService = null; 220 } 221 } 222 }; 223 224 private CameraOpenErrorCallback mCameraOpenErrorCallback = 225 new CameraOpenErrorCallback() { 226 @Override 227 public void onCameraDisabled(int cameraId) { 228 CameraUtil.showErrorAndFinish(CameraActivity.this, 229 R.string.camera_disabled); 230 } 231 232 @Override 233 public void onDeviceOpenFailure(int cameraId) { 234 CameraUtil.showErrorAndFinish(CameraActivity.this, 235 R.string.cannot_connect_camera); 236 } 237 238 @Override 239 public void onReconnectionFailure(CameraManager mgr) { 240 CameraUtil.showErrorAndFinish(CameraActivity.this, 241 R.string.cannot_connect_camera); 242 } 243 }; 244 245 // close activity when screen turns off 246 private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 247 @Override 248 public void onReceive(Context context, Intent intent) { 249 finish(); 250 } 251 }; 252 253 private static BroadcastReceiver sScreenOffReceiver; 254 255 private static class ScreenOffReceiver extends BroadcastReceiver { 256 @Override 257 public void onReceive(Context context, Intent intent) { 258 sFirstStartAfterScreenOn = true; 259 } 260 } 261 262 private class MainHandler extends Handler { 263 public MainHandler(Looper looper) { 264 super(looper); 265 } 266 267 @Override 268 public void handleMessage(Message msg) { 269 if (msg.what == HIDE_ACTION_BAR) { 270 removeMessages(HIDE_ACTION_BAR); 271 CameraActivity.this.setSystemBarsVisibility(false); 272 } 273 } 274 } 275 276 public interface OnActionBarVisibilityListener { 277 public void onActionBarVisibilityChanged(boolean isVisible); 278 } 279 280 public void setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener) { 281 mOnActionBarVisibilityListener = listener; 282 } 283 284 public static boolean isFirstStartAfterScreenOn() { 285 return sFirstStartAfterScreenOn; 286 } 287 288 public static void resetFirstStartAfterScreenOn() { 289 sFirstStartAfterScreenOn = false; 290 } 291 292 private FilmStripView.Listener mFilmStripListener = 293 new FilmStripView.Listener() { 294 @Override 295 public void onDataPromoted(int dataID) { 296 removeData(dataID); 297 } 298 299 @Override 300 public void onDataDemoted(int dataID) { 301 removeData(dataID); 302 } 303 304 @Override 305 public void onDataFullScreenChange(int dataID, boolean full) { 306 boolean isCameraID = isCameraPreview(dataID); 307 if (!isCameraID) { 308 if (!full) { 309 // Always show action bar in filmstrip mode 310 CameraActivity.this.setSystemBarsVisibility(true, false); 311 } else if (mActionBar.isShowing()) { 312 // Hide action bar after time out in full screen mode 313 mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, 314 SHOW_ACTION_BAR_TIMEOUT_MS); 315 } 316 } 317 } 318 319 /** 320 * Check if the local data corresponding to dataID is the camera 321 * preview. 322 * 323 * @param dataID the ID of the local data 324 * @return true if the local data is not null and it is the 325 * camera preview. 326 */ 327 private boolean isCameraPreview(int dataID) { 328 LocalData localData = mDataAdapter.getLocalData(dataID); 329 if (localData == null) { 330 Log.w(TAG, "Current data ID not found."); 331 return false; 332 } 333 return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW; 334 } 335 336 @Override 337 public void onReload() { 338 setPreviewControlsVisibility(true); 339 } 340 341 @Override 342 public void onCurrentDataCentered(int dataID) { 343 if (dataID != 0 && !mFilmStripView.isCameraPreview()) { 344 // For now, We ignore all items that are not the camera preview. 345 return; 346 } 347 348 if(!arePreviewControlsVisible()) { 349 setPreviewControlsVisibility(true); 350 } 351 } 352 353 @Override 354 public void onCurrentDataOffCentered(int dataID) { 355 if (dataID != 0 && !mFilmStripView.isCameraPreview()) { 356 // For now, We ignore all items that are not the camera preview. 357 return; 358 } 359 360 if (arePreviewControlsVisible()) { 361 setPreviewControlsVisibility(false); 362 } 363 } 364 365 @Override 366 public void onDataFocusChanged(final int dataID, final boolean focused) { 367 // Delay hiding action bar if there is any user interaction 368 if (mMainHandler.hasMessages(HIDE_ACTION_BAR)) { 369 mMainHandler.removeMessages(HIDE_ACTION_BAR); 370 mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, 371 SHOW_ACTION_BAR_TIMEOUT_MS); 372 } 373 // TODO: This callback is UI event callback, should always 374 // happen on UI thread. Find the reason for this 375 // runOnUiThread() and fix it. 376 runOnUiThread(new Runnable() { 377 @Override 378 public void run() { 379 LocalData currentData = mDataAdapter.getLocalData(dataID); 380 if (currentData == null) { 381 Log.w(TAG, "Current data ID not found."); 382 hidePanoStitchingProgress(); 383 return; 384 } 385 boolean isCameraID = currentData.getLocalDataType() == 386 LocalData.LOCAL_CAMERA_PREVIEW; 387 if (!focused) { 388 if (isCameraID) { 389 mCurrentModule.onPreviewFocusChanged(false); 390 CameraActivity.this.setSystemBarsVisibility(true); 391 } 392 hidePanoStitchingProgress(); 393 } else { 394 if (isCameraID) { 395 // Don't show the action bar in Camera 396 // preview. 397 CameraActivity.this.setSystemBarsVisibility(false); 398 399 if (mPendingDeletion) { 400 performDeletion(); 401 } 402 } else { 403 updateActionBarMenu(dataID); 404 } 405 406 Uri contentUri = currentData.getContentUri(); 407 if (contentUri == null) { 408 hidePanoStitchingProgress(); 409 return; 410 } 411 int panoStitchingProgress = mPanoramaManager.getTaskProgress( 412 contentUri); 413 if (panoStitchingProgress < 0) { 414 hidePanoStitchingProgress(); 415 return; 416 } 417 showPanoStitchingProgress(); 418 updateStitchingProgress(panoStitchingProgress); 419 } 420 } 421 }); 422 } 423 424 @Override 425 public void onToggleSystemDecorsVisibility(int dataID) { 426 // If action bar is showing, hide it immediately, otherwise 427 // show action bar and hide it later 428 if (mActionBar.isShowing()) { 429 CameraActivity.this.setSystemBarsVisibility(false); 430 } else { 431 // Don't show the action bar if that is the camera preview. 432 boolean isCameraID = isCameraPreview(dataID); 433 if (!isCameraID) { 434 CameraActivity.this.setSystemBarsVisibility(true, true); 435 } 436 } 437 } 438 439 @Override 440 public void setSystemDecorsVisibility(boolean visible) { 441 CameraActivity.this.setSystemBarsVisibility(visible); 442 } 443 }; 444 445 public void gotoGallery() { 446 mFilmStripView.getController().goToNextItem(); 447 } 448 449 /** 450 * If {@param visible} is false, this hides the action bar and switches the system UI 451 * to lights-out mode. 452 */ 453 // TODO: This should not be called outside of the activity. 454 public void setSystemBarsVisibility(boolean visible) { 455 setSystemBarsVisibility(visible, false); 456 } 457 458 /** 459 * If {@param visible} is false, this hides the action bar and switches the 460 * system UI to lights-out mode. If {@param hideLater} is true, a delayed message 461 * will be sent after a timeout to hide the action bar. 462 */ 463 private void setSystemBarsVisibility(boolean visible, boolean hideLater) { 464 mMainHandler.removeMessages(HIDE_ACTION_BAR); 465 boolean currentlyVisible = mActionBar.isShowing(); 466 467 if (visible != currentlyVisible) { 468 int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (visible ? View.SYSTEM_UI_FLAG_VISIBLE 469 : View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); 470 mAboveFilmstripControlLayout.setSystemUiVisibility(visibility); 471 472 if (visible) { 473 mActionBar.show(); 474 } else { 475 mActionBar.hide(); 476 } 477 if (mOnActionBarVisibilityListener != null) { 478 mOnActionBarVisibilityListener.onActionBarVisibilityChanged(visible); 479 } 480 } 481 482 // Now delay hiding the bars 483 if (visible && hideLater) { 484 mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS); 485 } 486 } 487 488 private void hidePanoStitchingProgress() { 489 mPanoStitchingPanel.setVisibility(View.GONE); 490 } 491 492 private void showPanoStitchingProgress() { 493 mPanoStitchingPanel.setVisibility(View.VISIBLE); 494 } 495 496 private void updateStitchingProgress(int progress) { 497 mBottomProgress.setProgress(progress); 498 } 499 500 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 501 private void setupNfcBeamPush() { 502 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this); 503 if (adapter == null) { 504 return; 505 } 506 507 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 508 // Disable beaming 509 adapter.setNdefPushMessage(null, CameraActivity.this); 510 return; 511 } 512 513 adapter.setBeamPushUris(null, CameraActivity.this); 514 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { 515 @Override 516 public Uri[] createBeamUris(NfcEvent event) { 517 return mNfcPushUris; 518 } 519 }, CameraActivity.this); 520 } 521 522 private void setNfcBeamPushUri(Uri uri) { 523 mNfcPushUris[0] = uri; 524 } 525 526 private void setStandardShareIntent(Uri contentUri, String mimeType) { 527 mStandardShareIntent = getShareIntentFromType(mimeType); 528 if (mStandardShareIntent != null) { 529 mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 530 mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 531 if (mStandardShareActionProvider != null) { 532 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 533 } 534 } 535 } 536 537 /** 538 * Get the share intent according to the mimeType 539 * 540 * @param mimeType The mimeType of current data. 541 * @return the video/image's ShareIntent or null if mimeType is invalid. 542 */ 543 private Intent getShareIntentFromType(String mimeType) { 544 // Lazily create the intent object. 545 if (mimeType.startsWith("video/")) { 546 if (mVideoShareIntent == null) { 547 mVideoShareIntent = new Intent(Intent.ACTION_SEND); 548 mVideoShareIntent.setType("video/*"); 549 } 550 return mVideoShareIntent; 551 } else if (mimeType.startsWith("image/")) { 552 if (mImageShareIntent == null) { 553 mImageShareIntent = new Intent(Intent.ACTION_SEND); 554 mImageShareIntent.setType("image/*"); 555 } 556 return mImageShareIntent; 557 } 558 Log.w(TAG, "unsupported mimeType " + mimeType); 559 return null; 560 } 561 562 private void setPanoramaShareIntent(Uri contentUri) { 563 if (mPanoramaShareIntent == null) { 564 mPanoramaShareIntent = new Intent(Intent.ACTION_SEND); 565 } 566 mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg"); 567 mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 568 if (mPanoramaShareActionProvider != null) { 569 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 570 } 571 } 572 573 @Override 574 public void onMenuVisibilityChanged(boolean isVisible) { 575 // If menu is showing, we need to make sure action bar does not go away. 576 mMainHandler.removeMessages(HIDE_ACTION_BAR); 577 if (!isVisible) { 578 mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS); 579 } 580 } 581 582 /** 583 * According to the data type, make the menu items for supported operations 584 * visible. 585 * 586 * @param dataID the data ID of the current item. 587 */ 588 private void updateActionBarMenu(int dataID) { 589 LocalData currentData = mDataAdapter.getLocalData(dataID); 590 if (currentData == null) { 591 return; 592 } 593 int type = currentData.getLocalDataType(); 594 595 if (mActionBarMenu == null) { 596 return; 597 } 598 599 int supported = 0; 600 601 switch (type) { 602 case LocalData.LOCAL_IMAGE: 603 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 604 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 605 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 606 break; 607 case LocalData.LOCAL_VIDEO: 608 supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM 609 | SUPPORT_SHARE; 610 break; 611 case LocalData.LOCAL_PHOTO_SPHERE: 612 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 613 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 614 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 615 break; 616 case LocalData.LOCAL_360_PHOTO_SPHERE: 617 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 618 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 619 | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360 620 | SUPPORT_SHOW_ON_MAP; 621 break; 622 default: 623 break; 624 } 625 626 // In secure camera mode, we only support delete operation. 627 if (isSecureCamera()) { 628 supported &= SUPPORT_DELETE; 629 } 630 631 setMenuItemVisible(mActionBarMenu, R.id.action_delete, 632 (supported & SUPPORT_DELETE) != 0); 633 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw, 634 (supported & SUPPORT_ROTATE) != 0); 635 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw, 636 (supported & SUPPORT_ROTATE) != 0); 637 setMenuItemVisible(mActionBarMenu, R.id.action_details, 638 (supported & SUPPORT_INFO) != 0); 639 setMenuItemVisible(mActionBarMenu, R.id.action_crop, 640 (supported & SUPPORT_CROP) != 0); 641 setMenuItemVisible(mActionBarMenu, R.id.action_setas, 642 (supported & SUPPORT_SETAS) != 0); 643 setMenuItemVisible(mActionBarMenu, R.id.action_edit, 644 (supported & SUPPORT_EDIT) != 0); 645 setMenuItemVisible(mActionBarMenu, R.id.action_trim, 646 (supported & SUPPORT_TRIM) != 0); 647 648 boolean standardShare = (supported & SUPPORT_SHARE) != 0; 649 boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0; 650 setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare); 651 setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare); 652 653 if (panoramaShare) { 654 // For 360 PhotoSphere, relegate standard share to the overflow menu 655 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 656 if (item != null) { 657 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 658 item.setTitle(getResources().getString(R.string.share_as_photo)); 659 } 660 // And, promote "share as panorama" to action bar 661 item = mActionBarMenu.findItem(R.id.action_share_panorama); 662 if (item != null) { 663 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 664 } 665 setPanoramaShareIntent(currentData.getContentUri()); 666 } 667 if (standardShare) { 668 if (!panoramaShare) { 669 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 670 if (item != null) { 671 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 672 item.setTitle(getResources().getString(R.string.share)); 673 } 674 } 675 setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType()); 676 setNfcBeamPushUri(currentData.getContentUri()); 677 } 678 679 boolean itemHasLocation = currentData.getLatLong() != null; 680 setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map, 681 itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0); 682 } 683 684 private void setMenuItemVisible(Menu menu, int itemId, boolean visible) { 685 MenuItem item = menu.findItem(itemId); 686 if (item != null) 687 item.setVisible(visible); 688 } 689 690 private ImageTaskManager.TaskListener mStitchingListener = 691 new ImageTaskManager.TaskListener() { 692 @Override 693 public void onTaskQueued(String filePath, final Uri imageUri) { 694 mMainHandler.post(new Runnable() { 695 @Override 696 public void run() { 697 notifyNewMedia(imageUri); 698 int dataID = mDataAdapter.findDataByContentUri(imageUri); 699 if (dataID != -1) { 700 // Don't allow special UI actions (swipe to 701 // delete, for example) on in-progress data. 702 LocalData d = mDataAdapter.getLocalData(dataID); 703 InProgressDataWrapper newData = new InProgressDataWrapper(d); 704 mDataAdapter.updateData(dataID, newData); 705 } 706 } 707 }); 708 } 709 710 @Override 711 public void onTaskDone(String filePath, final Uri imageUri) { 712 Log.v(TAG, "onTaskDone:" + filePath); 713 mMainHandler.post(new Runnable() { 714 @Override 715 public void run() { 716 int doneID = mDataAdapter.findDataByContentUri(imageUri); 717 int currentDataId = mFilmStripView.getCurrentId(); 718 719 if (currentDataId == doneID) { 720 hidePanoStitchingProgress(); 721 updateStitchingProgress(0); 722 } 723 724 mDataAdapter.refresh(getContentResolver(), imageUri); 725 } 726 }); 727 } 728 729 @Override 730 public void onTaskProgress( 731 String filePath, final Uri imageUri, final int progress) { 732 mMainHandler.post(new Runnable() { 733 @Override 734 public void run() { 735 int currentDataId = mFilmStripView.getCurrentId(); 736 if (currentDataId == -1) { 737 return; 738 } 739 if (imageUri.equals( 740 mDataAdapter.getLocalData(currentDataId).getContentUri())) { 741 updateStitchingProgress(progress); 742 } 743 } 744 }); 745 } 746 }; 747 748 public MediaSaveService getMediaSaveService() { 749 return mMediaSaveService; 750 } 751 752 public void notifyNewMedia(Uri uri) { 753 ContentResolver cr = getContentResolver(); 754 String mimeType = cr.getType(uri); 755 if (mimeType.startsWith("video/")) { 756 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 757 mDataAdapter.addNewVideo(cr, uri); 758 } else if (mimeType.startsWith("image/")) { 759 CameraUtil.broadcastNewPicture(this, uri); 760 mDataAdapter.addNewPhoto(cr, uri); 761 } else if (mimeType.startsWith("application/stitching-preview")) { 762 mDataAdapter.addNewPhoto(cr, uri); 763 } else { 764 android.util.Log.w(TAG, "Unknown new media with MIME type:" 765 + mimeType + ", uri:" + uri); 766 } 767 } 768 769 private void removeData(int dataID) { 770 mDataAdapter.removeData(CameraActivity.this, dataID); 771 if (mDataAdapter.getTotalNumber() > 1) { 772 showUndoDeletionBar(); 773 } else { 774 // If camera preview is the only view left in filmstrip, 775 // no need to show undo bar. 776 mPendingDeletion = true; 777 performDeletion(); 778 } 779 } 780 781 private void bindMediaSaveService() { 782 Intent intent = new Intent(this, MediaSaveService.class); 783 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 784 } 785 786 private void unbindMediaSaveService() { 787 if (mConnection != null) { 788 unbindService(mConnection); 789 } 790 } 791 792 @Override 793 public boolean onCreateOptionsMenu(Menu menu) { 794 // Inflate the menu items for use in the action bar 795 MenuInflater inflater = getMenuInflater(); 796 inflater.inflate(R.menu.operations, menu); 797 mActionBarMenu = menu; 798 799 // Configure the standard share action provider 800 MenuItem item = menu.findItem(R.id.action_share); 801 mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider(); 802 mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml"); 803 if (mStandardShareIntent != null) { 804 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 805 } 806 807 // Configure the panorama share action provider 808 item = menu.findItem(R.id.action_share_panorama); 809 mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider(); 810 mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml"); 811 if (mPanoramaShareIntent != null) { 812 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 813 } 814 815 return super.onCreateOptionsMenu(menu); 816 } 817 818 @Override 819 public boolean onOptionsItemSelected(MenuItem item) { 820 int currentDataId = mFilmStripView.getCurrentId(); 821 if (currentDataId < 0) { 822 return false; 823 } 824 final LocalData localData = mDataAdapter.getLocalData(currentDataId); 825 826 // Handle presses on the action bar items 827 switch (item.getItemId()) { 828 case android.R.id.home: 829 // ActionBar's Up/Home button was clicked 830 try { 831 if (!CameraUtil.launchGallery(CameraActivity.this)) { 832 mFilmStripView.getController().goToFirstItem(); 833 } 834 return true; 835 } catch (ActivityNotFoundException e) { 836 Log.w(TAG, "No activity found to handle APP_GALLERY category!"); 837 finish(); 838 } 839 case R.id.action_delete: 840 removeData(currentDataId); 841 return true; 842 case R.id.action_edit: 843 launchEditor(localData); 844 return true; 845 case R.id.action_trim: { 846 // This is going to be handled by the Gallery app. 847 Intent intent = new Intent(ACTION_TRIM_VIDEO); 848 LocalData currentData = mDataAdapter.getLocalData( 849 mFilmStripView.getCurrentId()); 850 intent.setData(currentData.getContentUri()); 851 // We need the file path to wrap this into a RandomAccessFile. 852 intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath()); 853 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 854 return true; 855 } 856 case R.id.action_rotate_ccw: 857 localData.rotate90Degrees(this, mDataAdapter, currentDataId, false); 858 return true; 859 case R.id.action_rotate_cw: 860 localData.rotate90Degrees(this, mDataAdapter, currentDataId, true); 861 return true; 862 case R.id.action_crop: { 863 Intent intent = new Intent(CropActivity.CROP_ACTION); 864 intent.setClass(this, CropActivity.class); 865 intent.setDataAndType(localData.getContentUri(), localData.getMimeType()) 866 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 867 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 868 return true; 869 } 870 case R.id.action_setas: { 871 Intent intent = new Intent(Intent.ACTION_ATTACH_DATA) 872 .setDataAndType(localData.getContentUri(), 873 localData.getMimeType()) 874 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 875 intent.putExtra("mimeType", intent.getType()); 876 startActivityForResult(Intent.createChooser( 877 intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW); 878 return true; 879 } 880 case R.id.action_details: 881 (new AsyncTask<Void, Void, MediaDetails>() { 882 @Override 883 protected MediaDetails doInBackground(Void... params) { 884 return localData.getMediaDetails(CameraActivity.this); 885 } 886 887 @Override 888 protected void onPostExecute(MediaDetails mediaDetails) { 889 if (mediaDetails != null) { 890 DetailsDialog.create(CameraActivity.this, mediaDetails).show(); 891 } 892 } 893 }).execute(); 894 return true; 895 case R.id.action_show_on_map: 896 double[] latLong = localData.getLatLong(); 897 if (latLong != null) { 898 CameraUtil.showOnMap(this, latLong); 899 } 900 return true; 901 default: 902 return super.onOptionsItemSelected(item); 903 } 904 } 905 906 private boolean isCaptureIntent() { 907 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 908 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 909 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 910 return true; 911 } else { 912 return false; 913 } 914 } 915 916 @Override 917 public void onCreate(Bundle state) { 918 super.onCreate(state); 919 GcamHelper.init(getContentResolver()); 920 921 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 922 setContentView(R.layout.camera_filmstrip); 923 mActionBar = getActionBar(); 924 mActionBar.addOnMenuVisibilityListener(this); 925 926 if (ApiHelper.HAS_ROTATION_ANIMATION) { 927 setRotationAnimation(); 928 } 929 930 mMainHandler = new MainHandler(getMainLooper()); 931 // Check if this is in the secure camera mode. 932 Intent intent = getIntent(); 933 String action = intent.getAction(); 934 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 935 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 936 mSecureCamera = true; 937 } else { 938 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 939 } 940 941 if (mSecureCamera) { 942 // Change the window flags so that secure camera can show when locked 943 Window win = getWindow(); 944 WindowManager.LayoutParams params = win.getAttributes(); 945 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 946 win.setAttributes(params); 947 948 // Filter for screen off so that we can finish secure camera activity 949 // when screen is off. 950 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 951 registerReceiver(mScreenOffReceiver, filter); 952 // TODO: This static screen off event receiver is a workaround to the 953 // double onResume() invocation (onResume->onPause->onResume). We should 954 // find a better solution to this. 955 if (sScreenOffReceiver == null) { 956 sScreenOffReceiver = new ScreenOffReceiver(); 957 registerReceiver(sScreenOffReceiver, filter); 958 } 959 } 960 mAboveFilmstripControlLayout = 961 (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout); 962 mAboveFilmstripControlLayout.setFitsSystemWindows(true); 963 // Hide action bar first since we are in full screen mode first, and 964 // switch the system UI to lights-out mode. 965 this.setSystemBarsVisibility(false); 966 mPanoramaManager = AppManagerFactory.getInstance(this) 967 .getPanoramaStitchingManager(); 968 mPanoramaManager.addTaskListener(mStitchingListener); 969 LayoutInflater inflater = getLayoutInflater(); 970 View rootLayout = inflater.inflate(R.layout.camera, null, false); 971 mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root); 972 mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel); 973 mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar); 974 mCameraPreviewData = new CameraPreviewData(rootLayout, 975 FilmStripView.ImageData.SIZE_FULL, 976 FilmStripView.ImageData.SIZE_FULL); 977 // Put a CameraPreviewData at the first position. 978 mWrappedDataAdapter = new FixedFirstDataAdapter( 979 new CameraDataAdapter(new ColorDrawable( 980 getResources().getColor(R.color.photo_placeholder))), 981 mCameraPreviewData); 982 mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view); 983 mFilmStripView.setViewGap( 984 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 985 mPanoramaViewHelper = new PanoramaViewHelper(this); 986 mPanoramaViewHelper.onCreate(); 987 mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper); 988 // Set up the camera preview first so the preview shows up ASAP. 989 mFilmStripView.setListener(mFilmStripListener); 990 991 int moduleIndex = -1; 992 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) 993 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { 994 moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX; 995 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 996 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 997 .getAction()) 998 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 999 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1000 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 1001 } else { 1002 // If the activity has not been started using an explicit intent, 1003 // read the module index from the last time the user changed modes 1004 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 1005 moduleIndex = prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1); 1006 if ((moduleIndex == ModuleSwitcher.GCAM_MODULE_INDEX && 1007 !GcamHelper.hasGcamCapture()) || moduleIndex < 0) { 1008 moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 1009 } 1010 } 1011 1012 mOrientationListener = new MyOrientationEventListener(this); 1013 setModuleFromIndex(moduleIndex); 1014 mCurrentModule.init(this, mCameraModuleRootView); 1015 1016 if (!mSecureCamera) { 1017 mDataAdapter = mWrappedDataAdapter; 1018 mFilmStripView.setDataAdapter(mDataAdapter); 1019 if (!isCaptureIntent()) { 1020 mDataAdapter.requestLoad(getContentResolver()); 1021 } 1022 } else { 1023 // Put a lock placeholder as the last image by setting its date to 1024 // 0. 1025 ImageView v = (ImageView) getLayoutInflater().inflate( 1026 R.layout.secure_album_placeholder, null); 1027 v.setOnClickListener(new View.OnClickListener() { 1028 @Override 1029 public void onClick(View view) { 1030 CameraUtil.launchGallery(CameraActivity.this); 1031 finish(); 1032 } 1033 }); 1034 mDataAdapter = new FixedLastDataAdapter( 1035 mWrappedDataAdapter, 1036 new SimpleViewData( 1037 v, 1038 v.getDrawable().getIntrinsicWidth(), 1039 v.getDrawable().getIntrinsicHeight(), 1040 0, 0)); 1041 // Flush out all the original data. 1042 mDataAdapter.flush(); 1043 mFilmStripView.setDataAdapter(mDataAdapter); 1044 } 1045 1046 setupNfcBeamPush(); 1047 1048 mLocalImagesObserver = new LocalMediaObserver(); 1049 mLocalVideosObserver = new LocalMediaObserver(); 1050 1051 getContentResolver().registerContentObserver( 1052 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1053 mLocalImagesObserver); 1054 getContentResolver().registerContentObserver( 1055 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1056 mLocalVideosObserver); 1057 } 1058 1059 private void setRotationAnimation() { 1060 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1061 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1062 Window win = getWindow(); 1063 WindowManager.LayoutParams winParams = win.getAttributes(); 1064 winParams.rotationAnimation = rotationAnimation; 1065 win.setAttributes(winParams); 1066 } 1067 1068 @Override 1069 public void onUserInteraction() { 1070 super.onUserInteraction(); 1071 mCurrentModule.onUserInteraction(); 1072 } 1073 1074 @Override 1075 public boolean dispatchTouchEvent(MotionEvent ev) { 1076 boolean result = super.dispatchTouchEvent(ev); 1077 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1078 // Real deletion is postponed until the next user interaction after 1079 // the gesture that triggers deletion. Until real deletion is performed, 1080 // users can click the undo button to bring back the image that they 1081 // chose to delete. 1082 if (mPendingDeletion && !mIsUndoingDeletion) { 1083 performDeletion(); 1084 } 1085 } 1086 return result; 1087 } 1088 1089 @Override 1090 public void onPause() { 1091 // Delete photos that are pending deletion 1092 performDeletion(); 1093 mOrientationListener.disable(); 1094 mCurrentModule.onPauseBeforeSuper(); 1095 super.onPause(); 1096 mCurrentModule.onPauseAfterSuper(); 1097 1098 mLocalImagesObserver.setActivityPaused(true); 1099 mLocalVideosObserver.setActivityPaused(true); 1100 } 1101 1102 @Override 1103 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1104 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) { 1105 mResetToPreviewOnResume = false; 1106 } else { 1107 super.onActivityResult(requestCode, resultCode, data); 1108 } 1109 } 1110 1111 @Override 1112 public void onResume() { 1113 // TODO: Handle this in OrientationManager. 1114 // Auto-rotate off 1115 if (Settings.System.getInt(getContentResolver(), 1116 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 1117 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 1118 mAutoRotateScreen = false; 1119 } else { 1120 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 1121 mAutoRotateScreen = true; 1122 } 1123 mOrientationListener.enable(); 1124 mCurrentModule.onResumeBeforeSuper(); 1125 super.onResume(); 1126 mCurrentModule.onResumeAfterSuper(); 1127 1128 setSwipingEnabled(true); 1129 1130 if (mResetToPreviewOnResume) { 1131 // Go to the preview on resume. 1132 mFilmStripView.getController().goToFirstItem(); 1133 } 1134 // Default is showing the preview, unless disabled by explicitly 1135 // starting an activity we want to return from to the filmstrip rather 1136 // than the preview. 1137 mResetToPreviewOnResume = true; 1138 1139 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 1140 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 1141 if (!mSecureCamera) { 1142 // If it's secure camera, requestLoad() should not be called 1143 // as it will load all the data. 1144 mDataAdapter.requestLoad(getContentResolver()); 1145 } 1146 } 1147 mLocalImagesObserver.setActivityPaused(false); 1148 mLocalVideosObserver.setActivityPaused(false); 1149 } 1150 1151 @Override 1152 public void onStart() { 1153 super.onStart(); 1154 bindMediaSaveService(); 1155 mPanoramaViewHelper.onStart(); 1156 } 1157 1158 @Override 1159 protected void onStop() { 1160 super.onStop(); 1161 mPanoramaViewHelper.onStop(); 1162 unbindMediaSaveService(); 1163 } 1164 1165 @Override 1166 public void onDestroy() { 1167 if (mSecureCamera) { 1168 unregisterReceiver(mScreenOffReceiver); 1169 } 1170 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 1171 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 1172 1173 super.onDestroy(); 1174 } 1175 1176 @Override 1177 public void onConfigurationChanged(Configuration config) { 1178 super.onConfigurationChanged(config); 1179 mCurrentModule.onConfigurationChanged(config); 1180 } 1181 1182 @Override 1183 public boolean onKeyDown(int keyCode, KeyEvent event) { 1184 if (mFilmStripView.inCameraFullscreen()) { 1185 if (mCurrentModule.onKeyDown(keyCode, event)) { 1186 return true; 1187 } 1188 // Prevent software keyboard or voice search from showing up. 1189 if (keyCode == KeyEvent.KEYCODE_SEARCH 1190 || keyCode == KeyEvent.KEYCODE_MENU) { 1191 if (event.isLongPress()) { 1192 return true; 1193 } 1194 } 1195 } 1196 1197 return super.onKeyDown(keyCode, event); 1198 } 1199 1200 @Override 1201 public boolean onKeyUp(int keyCode, KeyEvent event) { 1202 if (mFilmStripView.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) { 1203 return true; 1204 } 1205 return super.onKeyUp(keyCode, event); 1206 } 1207 1208 @Override 1209 public void onBackPressed() { 1210 if (!mFilmStripView.inCameraFullscreen()) { 1211 mFilmStripView.getController().goToFirstItem(); 1212 } else if (!mCurrentModule.onBackPressed()) { 1213 super.onBackPressed(); 1214 } 1215 } 1216 1217 public boolean isAutoRotateScreen() { 1218 return mAutoRotateScreen; 1219 } 1220 1221 protected void updateStorageSpace() { 1222 mStorageSpaceBytes = Storage.getAvailableSpace(); 1223 } 1224 1225 protected long getStorageSpaceBytes() { 1226 return mStorageSpaceBytes; 1227 } 1228 1229 protected void updateStorageSpaceAndHint() { 1230 updateStorageSpace(); 1231 updateStorageHint(mStorageSpaceBytes); 1232 } 1233 1234 protected void updateStorageHint(long storageSpace) { 1235 String message = null; 1236 if (storageSpace == Storage.UNAVAILABLE) { 1237 message = getString(R.string.no_storage); 1238 } else if (storageSpace == Storage.PREPARING) { 1239 message = getString(R.string.preparing_sd); 1240 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 1241 message = getString(R.string.access_sd_fail); 1242 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 1243 message = getString(R.string.spaceIsLow_content); 1244 } 1245 1246 if (message != null) { 1247 if (mStorageHint == null) { 1248 mStorageHint = OnScreenHint.makeText(this, message); 1249 } else { 1250 mStorageHint.setText(message); 1251 } 1252 mStorageHint.show(); 1253 } else if (mStorageHint != null) { 1254 mStorageHint.cancel(); 1255 mStorageHint = null; 1256 } 1257 } 1258 1259 protected void setResultEx(int resultCode) { 1260 mResultCodeForTesting = resultCode; 1261 setResult(resultCode); 1262 } 1263 1264 protected void setResultEx(int resultCode, Intent data) { 1265 mResultCodeForTesting = resultCode; 1266 mResultDataForTesting = data; 1267 setResult(resultCode, data); 1268 } 1269 1270 public int getResultCode() { 1271 return mResultCodeForTesting; 1272 } 1273 1274 public Intent getResultData() { 1275 return mResultDataForTesting; 1276 } 1277 1278 public boolean isSecureCamera() { 1279 return mSecureCamera; 1280 } 1281 1282 @Override 1283 public void onModuleSelected(int moduleIndex) { 1284 if (mCurrentModuleIndex == moduleIndex) { 1285 return; 1286 } 1287 1288 CameraHolder.instance().keep(); 1289 closeModule(mCurrentModule); 1290 setModuleFromIndex(moduleIndex); 1291 1292 openModule(mCurrentModule); 1293 mCurrentModule.onOrientationChanged(mLastRawOrientation); 1294 if (mMediaSaveService != null) { 1295 mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); 1296 } 1297 1298 // Store the module index so we can use it the next time the Camera 1299 // starts up. 1300 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 1301 prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, moduleIndex).apply(); 1302 } 1303 1304 /** 1305 * Sets the mCurrentModuleIndex, creates a new module instance for the given 1306 * index an sets it as mCurrentModule. 1307 */ 1308 private void setModuleFromIndex(int moduleIndex) { 1309 mCurrentModuleIndex = moduleIndex; 1310 switch (moduleIndex) { 1311 case ModuleSwitcher.VIDEO_MODULE_INDEX: 1312 mCurrentModule = new VideoModule(); 1313 break; 1314 1315 case ModuleSwitcher.PHOTO_MODULE_INDEX: 1316 mCurrentModule = new PhotoModule(); 1317 break; 1318 1319 case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX: 1320 mCurrentModule = new WideAnglePanoramaModule(); 1321 break; 1322 1323 case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: 1324 mCurrentModule = PhotoSphereHelper.createPanoramaModule(); 1325 break; 1326 case ModuleSwitcher.GCAM_MODULE_INDEX: 1327 // Force immediate release of Camera instance 1328 CameraHolder.instance().strongRelease(); 1329 mCurrentModule = GcamHelper.createGcamModule(); 1330 break; 1331 default: 1332 // Fall back to photo mode. 1333 mCurrentModule = new PhotoModule(); 1334 mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; 1335 break; 1336 } 1337 } 1338 1339 /** 1340 * Launches an ACTION_EDIT intent for the given local data item. 1341 */ 1342 public void launchEditor(LocalData data) { 1343 Intent intent = new Intent(Intent.ACTION_EDIT) 1344 .setDataAndType(data.getContentUri(), data.getMimeType()) 1345 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1346 startActivityForResult(Intent.createChooser(intent, null), 1347 REQ_CODE_DONT_SWITCH_TO_PREVIEW); 1348 } 1349 1350 /** 1351 * Launch the tiny planet editor. 1352 * 1353 * @param data the data must be a 360 degree stereographically mapped 1354 * panoramic image. It will not be modified, instead a new item 1355 * with the result will be added to the filmstrip. 1356 */ 1357 public void launchTinyPlanetEditor(LocalData data) { 1358 TinyPlanetFragment fragment = new TinyPlanetFragment(); 1359 Bundle bundle = new Bundle(); 1360 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString()); 1361 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 1362 fragment.setArguments(bundle); 1363 fragment.show(getFragmentManager(), "tiny_planet"); 1364 } 1365 1366 private void openModule(CameraModule module) { 1367 module.init(this, mCameraModuleRootView); 1368 module.onResumeBeforeSuper(); 1369 module.onResumeAfterSuper(); 1370 } 1371 1372 private void closeModule(CameraModule module) { 1373 module.onPauseBeforeSuper(); 1374 module.onPauseAfterSuper(); 1375 ((ViewGroup) mCameraModuleRootView).removeAllViews(); 1376 } 1377 1378 private void performDeletion() { 1379 if (!mPendingDeletion) { 1380 return; 1381 } 1382 hideUndoDeletionBar(false); 1383 mDataAdapter.executeDeletion(CameraActivity.this); 1384 updateActionBarMenu(mFilmStripView.getCurrentId()); 1385 } 1386 1387 public void showUndoDeletionBar() { 1388 if (mPendingDeletion) { 1389 performDeletion(); 1390 } 1391 Log.v(TAG, "showing undo bar"); 1392 mPendingDeletion = true; 1393 if (mUndoDeletionBar == null) { 1394 ViewGroup v = (ViewGroup) getLayoutInflater().inflate( 1395 R.layout.undo_bar, mAboveFilmstripControlLayout, true); 1396 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 1397 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 1398 button.setOnClickListener(new View.OnClickListener() { 1399 @Override 1400 public void onClick(View view) { 1401 mDataAdapter.undoDataRemoval(); 1402 hideUndoDeletionBar(true); 1403 } 1404 }); 1405 // Setting undo bar clickable to avoid touch events going through 1406 // the bar to the buttons (eg. edit button, etc) underneath the bar. 1407 mUndoDeletionBar.setClickable(true); 1408 // When there is user interaction going on with the undo button, we 1409 // do not want to hide the undo bar. 1410 button.setOnTouchListener(new View.OnTouchListener() { 1411 @Override 1412 public boolean onTouch(View v, MotionEvent event) { 1413 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 1414 mIsUndoingDeletion = true; 1415 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 1416 mIsUndoingDeletion =false; 1417 } 1418 return false; 1419 } 1420 }); 1421 } 1422 mUndoDeletionBar.setAlpha(0f); 1423 mUndoDeletionBar.setVisibility(View.VISIBLE); 1424 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 1425 } 1426 1427 private void hideUndoDeletionBar(boolean withAnimation) { 1428 Log.v(TAG, "Hiding undo deletion bar"); 1429 mPendingDeletion = false; 1430 if (mUndoDeletionBar != null) { 1431 if (withAnimation) { 1432 mUndoDeletionBar.animate() 1433 .setDuration(200) 1434 .alpha(0f) 1435 .setListener(new Animator.AnimatorListener() { 1436 @Override 1437 public void onAnimationStart(Animator animation) { 1438 // Do nothing. 1439 } 1440 1441 @Override 1442 public void onAnimationEnd(Animator animation) { 1443 mUndoDeletionBar.setVisibility(View.GONE); 1444 } 1445 1446 @Override 1447 public void onAnimationCancel(Animator animation) { 1448 // Do nothing. 1449 } 1450 1451 @Override 1452 public void onAnimationRepeat(Animator animation) { 1453 // Do nothing. 1454 } 1455 }) 1456 .start(); 1457 } else { 1458 mUndoDeletionBar.setVisibility(View.GONE); 1459 } 1460 } 1461 } 1462 1463 @Override 1464 public void onShowSwitcherPopup() { 1465 } 1466 1467 /** 1468 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 1469 * capture intent. 1470 * 1471 * @param enable {@code true} to enable swipe. 1472 */ 1473 public void setSwipingEnabled(boolean enable) { 1474 if (isCaptureIntent()) { 1475 mCameraPreviewData.lockPreview(true); 1476 } else { 1477 mCameraPreviewData.lockPreview(!enable); 1478 } 1479 } 1480 1481 1482 /** 1483 * Check whether camera controls are visible. 1484 * 1485 * @return whether controls are visible. 1486 */ 1487 private boolean arePreviewControlsVisible() { 1488 return mCurrentModule.arePreviewControlsVisible(); 1489 } 1490 1491 /** 1492 * Show or hide the {@link CameraControls} using the current module's 1493 * implementation of {@link #onPreviewFocusChanged}. 1494 * 1495 * @param showControls whether to show camera controls. 1496 */ 1497 private void setPreviewControlsVisibility(boolean showControls) { 1498 mCurrentModule.onPreviewFocusChanged(showControls); 1499 } 1500 1501 // Accessor methods for getting latency times used in performance testing 1502 public long getAutoFocusTime() { 1503 return (mCurrentModule instanceof PhotoModule) ? 1504 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 1505 } 1506 1507 public long getShutterLag() { 1508 return (mCurrentModule instanceof PhotoModule) ? 1509 ((PhotoModule) mCurrentModule).mShutterLag : -1; 1510 } 1511 1512 public long getShutterToPictureDisplayedTime() { 1513 return (mCurrentModule instanceof PhotoModule) ? 1514 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 1515 } 1516 1517 public long getPictureDisplayedToJpegCallbackTime() { 1518 return (mCurrentModule instanceof PhotoModule) ? 1519 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 1520 } 1521 1522 public long getJpegCallbackFinishTime() { 1523 return (mCurrentModule instanceof PhotoModule) ? 1524 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 1525 } 1526 1527 public long getCaptureStartTime() { 1528 return (mCurrentModule instanceof PhotoModule) ? 1529 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 1530 } 1531 1532 public boolean isRecording() { 1533 return (mCurrentModule instanceof VideoModule) ? 1534 ((VideoModule) mCurrentModule).isRecording() : false; 1535 } 1536 1537 public CameraOpenErrorCallback getCameraOpenErrorCallback() { 1538 return mCameraOpenErrorCallback; 1539 } 1540 1541 // For debugging purposes only. 1542 public CameraModule getCurrentModule() { 1543 return mCurrentModule; 1544 } 1545 } 1546