CameraActivity.java revision b2510252fb020f3ccb88787c870930427377b9df
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.animation.Animator; 20import android.annotation.TargetApi; 21import android.app.ActionBar; 22import android.app.Activity; 23import android.app.AlertDialog; 24import android.content.ActivityNotFoundException; 25import android.content.BroadcastReceiver; 26import android.content.ContentResolver; 27import android.content.Context; 28import android.content.Intent; 29import android.content.IntentFilter; 30import android.content.SharedPreferences; 31import android.content.pm.ActivityInfo; 32import android.content.res.Configuration; 33import android.graphics.Bitmap; 34import android.graphics.Color; 35import android.graphics.SurfaceTexture; 36import android.graphics.drawable.ColorDrawable; 37import android.net.Uri; 38import android.nfc.NfcAdapter; 39import android.nfc.NfcAdapter.CreateBeamUrisCallback; 40import android.nfc.NfcEvent; 41import android.os.AsyncTask; 42import android.os.Build; 43import android.os.Bundle; 44import android.os.Handler; 45import android.os.Looper; 46import android.os.Message; 47import android.preference.PreferenceManager; 48import android.provider.MediaStore; 49import android.provider.Settings; 50import android.util.Log; 51import android.view.Gravity; 52import android.view.KeyEvent; 53import android.view.LayoutInflater; 54import android.view.Menu; 55import android.view.MenuInflater; 56import android.view.MenuItem; 57import android.view.MotionEvent; 58import android.view.View; 59import android.view.ViewGroup; 60import android.view.Window; 61import android.view.WindowManager; 62import android.widget.FrameLayout; 63import android.widget.FrameLayout.LayoutParams; 64import android.widget.ImageView; 65import android.widget.PopupWindow; 66import android.widget.ProgressBar; 67import android.widget.ShareActionProvider; 68 69import com.android.camera.app.AppController; 70import com.android.camera.app.AppManagerFactory; 71import com.android.camera.app.CameraAppUI; 72import com.android.camera.app.CameraController; 73import com.android.camera.app.CameraManager; 74import com.android.camera.app.CameraManagerFactory; 75import com.android.camera.app.CameraProvider; 76import com.android.camera.app.CameraServices; 77import com.android.camera.app.ImageTaskManager; 78import com.android.camera.app.MediaSaver; 79import com.android.camera.app.ModuleManagerImpl; 80import com.android.camera.app.OrientationManager; 81import com.android.camera.app.OrientationManagerImpl; 82import com.android.camera.app.PanoramaStitchingManager; 83import com.android.camera.app.PlaceholderManager; 84import com.android.camera.crop.CropActivity; 85import com.android.camera.data.CameraDataAdapter; 86import com.android.camera.data.FixedLastDataAdapter; 87import com.android.camera.data.InProgressDataWrapper; 88import com.android.camera.data.LocalData; 89import com.android.camera.data.LocalDataAdapter; 90import com.android.camera.data.LocalMediaObserver; 91import com.android.camera.data.MediaDetails; 92import com.android.camera.data.SimpleViewData; 93import com.android.camera.filmstrip.FilmstripContentPanel; 94import com.android.camera.filmstrip.FilmstripController; 95import com.android.camera.module.ModulesInfo; 96import com.android.camera.settings.SettingsManager; 97import com.android.camera.settings.SettingsManager.SettingsCapabilities; 98import com.android.camera.tinyplanet.TinyPlanetFragment; 99import com.android.camera.ui.DetailsDialog; 100import com.android.camera.widget.FilmstripView; 101import com.android.camera.ui.MainActivityLayout; 102import com.android.camera.ui.ModeListView; 103import com.android.camera.ui.PreviewStatusListener; 104import com.android.camera.ui.SettingsView; 105import com.android.camera.util.ApiHelper; 106import com.android.camera.util.CameraUtil; 107import com.android.camera.util.FeedbackHelper; 108import com.android.camera.util.GcamHelper; 109import com.android.camera.util.IntentHelper; 110import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 111import com.android.camera.util.UsageStatistics; 112import com.android.camera2.R; 113 114import java.io.File; 115 116public class CameraActivity extends Activity 117 implements AppController, CameraManager.CameraOpenCallback, 118 ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener, 119 OrientationManager.OnOrientationChangeListener { 120 121 private static final String TAG = "CAM_Activity"; 122 123 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 124 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 125 public static final String ACTION_IMAGE_CAPTURE_SECURE = 126 "android.media.action.IMAGE_CAPTURE_SECURE"; 127 public static final String ACTION_TRIM_VIDEO = 128 "com.android.camera.action.TRIM"; 129 public static final String MEDIA_ITEM_PATH = "media-item-path"; 130 131 // The intent extra for camera from secure lock screen. True if the gallery 132 // should only show newly captured pictures. sSecureAlbumId does not 133 // increment. This is used when switching between camera, camcorder, and 134 // panorama. If the extra is not set, it is in the normal camera mode. 135 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 136 137 /** 138 * Request code from an activity we started that indicated that we do not want 139 * to reset the view to the preview in onResume. 140 */ 141 public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142; 142 143 public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999; 144 145 private static final int MSG_HIDE_ACTION_BAR = 1; 146 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2; 147 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins. 148 149 /** 150 * Whether onResume should reset the view to the preview. 151 */ 152 private boolean mResetToPreviewOnResume = true; 153 154 // Supported operations at FilmStripView. Different data has different 155 // set of supported operations. 156 private static final int SUPPORT_DELETE = 1 << 0; 157 private static final int SUPPORT_ROTATE = 1 << 1; 158 private static final int SUPPORT_INFO = 1 << 2; 159 private static final int SUPPORT_CROP = 1 << 3; 160 private static final int SUPPORT_SETAS = 1 << 4; 161 private static final int SUPPORT_EDIT = 1 << 5; 162 private static final int SUPPORT_TRIM = 1 << 6; 163 private static final int SUPPORT_SHARE = 1 << 7; 164 private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8; 165 private static final int SUPPORT_SHOW_ON_MAP = 1 << 9; 166 private static final int SUPPORT_ALL = 0xffffffff; 167 168 /** 169 * This data adapter is used by FilmStripView. 170 */ 171 private LocalDataAdapter mDataAdapter; 172 173 private SettingsManager mSettingsManager; 174 private SettingsController mSettingsController; 175 private PanoramaStitchingManager mPanoramaManager; 176 private PlaceholderManager mPlaceholderManager; 177 private ModeListView mModeListView; 178 private int mCurrentModeIndex; 179 private CameraModule mCurrentModule; 180 private ModuleManagerImpl mModuleManager; 181 private FrameLayout mAboveFilmstripControlLayout; 182 private FilmstripController mFilmstripController; 183 private boolean mFilmstripVisible; 184 private ProgressBar mBottomProgress; 185 private View mPanoStitchingPanel; 186 private int mResultCodeForTesting; 187 private Intent mResultDataForTesting; 188 private OnScreenHint mStorageHint; 189 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES; 190 private boolean mAutoRotateScreen; 191 private boolean mSecureCamera; 192 private int mLastRawOrientation; 193 private OrientationManagerImpl mOrientationManager; 194 private LocationManager mLocationManager; 195 private ButtonManager mButtonManager; 196 private Handler mMainHandler; 197 private PanoramaViewHelper mPanoramaViewHelper; 198 private ActionBar mActionBar; 199 private Menu mActionBarMenu; 200 private ViewGroup mUndoDeletionBar; 201 private boolean mIsUndoingDeletion = false; 202 203 private final Uri[] mNfcPushUris = new Uri[1]; 204 205 private ShareActionProvider mStandardShareActionProvider; 206 private Intent mStandardShareIntent; 207 private ShareActionProvider mPanoramaShareActionProvider; 208 private Intent mPanoramaShareIntent; 209 private LocalMediaObserver mLocalImagesObserver; 210 private LocalMediaObserver mLocalVideosObserver; 211 212 private boolean mPendingDeletion = false; 213 214 private Intent mVideoShareIntent; 215 private Intent mImageShareIntent; 216 217 private CameraController mCameraController; 218 private boolean mPaused; 219 private CameraAppUI mCameraAppUI; 220 221 private MediaSaver mMediaSaver; 222 223 private FeedbackHelper mFeedbackHelper; 224 225 // close activity when screen turns off 226 private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 227 @Override 228 public void onReceive(Context context, Intent intent) { 229 finish(); 230 } 231 }; 232 233 private static BroadcastReceiver sScreenOffReceiver; 234 235 /** 236 * Whether the screen is kept turned on. 237 */ 238 private boolean mKeepScreenOn; 239 private int mLastLayoutOrientation; 240 private CameraAppUI.BottomControls.Listener mMyFilmstripBottomControlListener = 241 new CameraAppUI.BottomControls.Listener() { 242 243 /** 244 * If the current photo is a photo sphere, this will launch the Photo Sphere 245 * panorama viewer. 246 */ 247 @Override 248 public void onView() { 249 LocalData data = getCurrentLocalData(); 250 if (data != null) { 251 data.view(mPanoramaViewHelper); 252 } 253 } 254 255 @Override 256 public void onEdit() { 257 LocalData data = getCurrentLocalData(); 258 if (data == null) { 259 return; 260 } 261 launchEditor(data); 262 } 263 264 @Override 265 public void onTinyPlanet() { 266 LocalData data = getCurrentLocalData(); 267 if (data == null) { 268 return; 269 } 270 launchTinyPlanetEditor(data); 271 } 272 273 private LocalData getCurrentLocalData() { 274 return mDataAdapter.getLocalData(mFilmstripController.getCurrentId()); 275 } 276 }; 277 278 @Override 279 public void onCameraOpened(CameraManager.CameraProxy camera) { 280 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) { 281 // We shouldn't be here. Just close the camera and leave. 282 camera.release(false); 283 throw new IllegalStateException("Camera opened but the module shouldn't be " + 284 "requesting"); 285 } 286 if (mCurrentModule != null) { 287 SettingsCapabilities capabilities = 288 SettingsController.getSettingsCapabilities(camera); 289 mSettingsManager.changeCamera(camera.getCameraId(), capabilities); 290 mCurrentModule.onCameraAvailable(camera); 291 } 292 } 293 294 @Override 295 public void onCameraDisabled(int cameraId) { 296 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_OPEN_FAIL, 297 "security"); 298 299 CameraUtil.showErrorAndFinish(this, R.string.camera_disabled); 300 } 301 302 @Override 303 public void onDeviceOpenFailure(int cameraId) { 304 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 305 UsageStatistics.ACTION_OPEN_FAIL, "open"); 306 307 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera); 308 } 309 310 @Override 311 public void onReconnectionFailure(CameraManager mgr) { 312 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 313 UsageStatistics.ACTION_OPEN_FAIL, "reconnect"); 314 315 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera); 316 } 317 318 private class MainHandler extends Handler { 319 public MainHandler(Looper looper) { 320 super(looper); 321 } 322 323 @Override 324 public void handleMessage(Message msg) { 325 switch (msg.what) { 326 case MSG_HIDE_ACTION_BAR: { 327 removeMessages(MSG_HIDE_ACTION_BAR); 328 CameraActivity.this.setFilmstripUiVisibility(false); 329 break; 330 } 331 332 case MSG_CLEAR_SCREEN_ON_FLAG: { 333 if (!mPaused) { 334 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 335 } 336 break; 337 } 338 339 default: 340 } 341 } 342 } 343 344 private String fileNameFromDataID(int dataID) { 345 final LocalData localData = mDataAdapter.getLocalData(dataID); 346 347 File localFile = new File(localData.getPath()); 348 return localFile.getName(); 349 } 350 351 private final FilmstripContentPanel.Listener mFilmstripListener = 352 new FilmstripContentPanel.Listener() { 353 354 @Override 355 public void onFilmstripHidden() { 356 mFilmstripVisible = false; 357 CameraActivity.this.setFilmstripUiVisibility(false); 358 // When the user hide the filmstrip (either swipe out or 359 // tap on back key) we move to the first item so next time 360 // when the user swipe in the filmstrip, the most recent 361 // one is shown. 362 mFilmstripController.goToFirstItem(); 363 if (mCurrentModule != null) { 364 mCurrentModule.onPreviewVisibilityChanged(true); 365 } 366 } 367 368 @Override 369 public void onFilmstripShown() { 370 mFilmstripVisible = true; 371 updateUiByData(mFilmstripController.getCurrentId()); 372 if (mCurrentModule != null) { 373 mCurrentModule.onPreviewVisibilityChanged(false); 374 } 375 } 376 377 @Override 378 public void onDataPromoted(int dataID) { 379 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 380 UsageStatistics.ACTION_DELETE, "promoted", 0, 381 UsageStatistics.hashFileName(fileNameFromDataID(dataID))); 382 383 removeData(dataID); 384 } 385 386 @Override 387 public void onDataDemoted(int dataID) { 388 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 389 UsageStatistics.ACTION_DELETE, "demoted", 0, 390 UsageStatistics.hashFileName(fileNameFromDataID(dataID))); 391 392 removeData(dataID); 393 } 394 395 @Override 396 public void onEnterFullScreen(int dataId) { 397 if (mFilmstripVisible) { 398 CameraActivity.this.setFilmstripUiVisibility(false); 399 } 400 } 401 402 @Override 403 public void onLeaveFullScreen(int dataId) { 404 // Do nothing. 405 } 406 407 @Override 408 public void onEnterFilmstrip(int dataId) { 409 if (mFilmstripVisible) { 410 CameraActivity.this.setFilmstripUiVisibility(true); 411 } 412 } 413 414 @Override 415 public void onLeaveFilmstrip(int dataId) { 416 // Do nothing. 417 } 418 419 @Override 420 public void onDataReloaded() { 421 if (!mFilmstripVisible) { 422 return; 423 } 424 updateUiByData(mFilmstripController.getCurrentId()); 425 } 426 427 @Override 428 public void onEnterZoomView(int dataID) { 429 if (mFilmstripVisible) { 430 CameraActivity.this.setFilmstripUiVisibility(false); 431 } 432 } 433 434 @Override 435 public void onDataFocusChanged(final int prevDataId, final int newDataId) { 436 if (!mFilmstripVisible) { 437 return; 438 } 439 // TODO: This callback is UI event callback, should always 440 // happen on UI thread. Find the reason for this 441 // runOnUiThread() and fix it. 442 runOnUiThread(new Runnable() { 443 @Override 444 public void run() { 445 updateUiByData(newDataId); 446 } 447 }); 448 } 449 450 }; 451 452 public void gotoGallery() { 453 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP, 454 "thumbnailTap"); 455 456 mFilmstripController.goToNextItem(); 457 } 458 459 /** 460 * If {@param visible} is false, this hides the action bar and switches the 461 * filmstrip UI to lights-out mode. 462 */ 463 // TODO: This should not be called outside of the activity. 464 public void setFilmstripUiVisibility(boolean visible) { 465 mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR); 466 467 int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility(); 468 int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE : 469 View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); 470 if (newSystemUIVisibility != currentSystemUIVisibility) { 471 mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility); 472 } 473 474 boolean currentActionBarVisibility = mActionBar.isShowing(); 475 mCameraAppUI.getFilmstripBottomControls().setVisible(visible); 476 if (visible != currentActionBarVisibility) { 477 if (visible) { 478 mActionBar.show(); 479 } else { 480 mActionBar.hide(); 481 } 482 } 483 } 484 485 private void hidePanoStitchingProgress() { 486 mPanoStitchingPanel.setVisibility(View.GONE); 487 } 488 489 private void showPanoStitchingProgress() { 490 mPanoStitchingPanel.setVisibility(View.VISIBLE); 491 } 492 493 private void updateStitchingProgress(int progress) { 494 mBottomProgress.setProgress(progress); 495 } 496 497 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 498 private void setupNfcBeamPush() { 499 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this); 500 if (adapter == null) { 501 return; 502 } 503 504 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 505 // Disable beaming 506 adapter.setNdefPushMessage(null, CameraActivity.this); 507 return; 508 } 509 510 adapter.setBeamPushUris(null, CameraActivity.this); 511 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { 512 @Override 513 public Uri[] createBeamUris(NfcEvent event) { 514 return mNfcPushUris; 515 } 516 }, CameraActivity.this); 517 } 518 519 private void setNfcBeamPushUri(Uri uri) { 520 mNfcPushUris[0] = uri; 521 } 522 523 private void setStandardShareIntent(Uri contentUri, String mimeType) { 524 mStandardShareIntent = getShareIntentFromType(mimeType); 525 if (mStandardShareIntent != null) { 526 mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 527 mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 528 if (mStandardShareActionProvider != null) { 529 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 530 } 531 } 532 } 533 534 /** 535 * Get the share intent according to the mimeType 536 * 537 * @param mimeType The mimeType of current data. 538 * @return the video/image's ShareIntent or null if mimeType is invalid. 539 */ 540 private Intent getShareIntentFromType(String mimeType) { 541 // Lazily create the intent object. 542 if (mimeType.startsWith("video/")) { 543 if (mVideoShareIntent == null) { 544 mVideoShareIntent = new Intent(Intent.ACTION_SEND); 545 mVideoShareIntent.setType("video/*"); 546 } 547 return mVideoShareIntent; 548 } else if (mimeType.startsWith("image/")) { 549 if (mImageShareIntent == null) { 550 mImageShareIntent = new Intent(Intent.ACTION_SEND); 551 mImageShareIntent.setType("image/*"); 552 } 553 return mImageShareIntent; 554 } 555 Log.w(TAG, "unsupported mimeType " + mimeType); 556 return null; 557 } 558 559 private void setPanoramaShareIntent(Uri contentUri) { 560 if (mPanoramaShareIntent == null) { 561 mPanoramaShareIntent = new Intent(Intent.ACTION_SEND); 562 } 563 mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg"); 564 mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); 565 if (mPanoramaShareActionProvider != null) { 566 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 567 } 568 } 569 570 @Override 571 public void onMenuVisibilityChanged(boolean isVisible) { 572 // TODO: Remove this or bring back the original implementation: cancel 573 // auto-hide actionbar. 574 } 575 576 @Override 577 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) { 578 int currentDataId = mFilmstripController.getCurrentId(); 579 if (currentDataId < 0) { 580 return false; 581 } 582 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE, 583 intent.getComponent().getPackageName(), 0, 584 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); 585 return true; 586 } 587 588 /** 589 * According to the data type, make the menu items for supported operations 590 * visible. 591 * 592 * @param dataID the data ID of the current item. 593 */ 594 private void updateActionBarMenu(int dataID) { 595 LocalData currentData = mDataAdapter.getLocalData(dataID); 596 if (currentData == null) { 597 return; 598 } 599 int type = currentData.getLocalDataType(); 600 601 if (mActionBarMenu == null) { 602 return; 603 } 604 605 int supported = 0; 606 607 switch (type) { 608 case LocalData.LOCAL_IMAGE: 609 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 610 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 611 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 612 break; 613 case LocalData.LOCAL_VIDEO: 614 supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM 615 | SUPPORT_SHARE; 616 break; 617 case LocalData.LOCAL_PHOTO_SPHERE: 618 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 619 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 620 | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP; 621 break; 622 case LocalData.LOCAL_360_PHOTO_SPHERE: 623 supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO 624 | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT 625 | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360 626 | SUPPORT_SHOW_ON_MAP; 627 break; 628 default: 629 break; 630 } 631 632 // In secure camera mode, we only support delete operation. 633 if (isSecureCamera()) { 634 supported &= SUPPORT_DELETE; 635 } 636 637 setMenuItemVisible(mActionBarMenu, R.id.action_delete, 638 (supported & SUPPORT_DELETE) != 0); 639 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw, 640 (supported & SUPPORT_ROTATE) != 0); 641 setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw, 642 (supported & SUPPORT_ROTATE) != 0); 643 setMenuItemVisible(mActionBarMenu, R.id.action_details, 644 (supported & SUPPORT_INFO) != 0); 645 setMenuItemVisible(mActionBarMenu, R.id.action_crop, 646 (supported & SUPPORT_CROP) != 0); 647 setMenuItemVisible(mActionBarMenu, R.id.action_setas, 648 (supported & SUPPORT_SETAS) != 0); 649 setMenuItemVisible(mActionBarMenu, R.id.action_edit, 650 (supported & SUPPORT_EDIT) != 0); 651 setMenuItemVisible(mActionBarMenu, R.id.action_trim, 652 (supported & SUPPORT_TRIM) != 0); 653 654 boolean standardShare = (supported & SUPPORT_SHARE) != 0; 655 boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0; 656 setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare); 657 setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare); 658 659 if (panoramaShare) { 660 // For 360 PhotoSphere, relegate standard share to the overflow menu 661 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 662 if (item != null) { 663 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 664 item.setTitle(getResources().getString(R.string.share_as_photo)); 665 } 666 // And, promote "share as panorama" to action bar 667 item = mActionBarMenu.findItem(R.id.action_share_panorama); 668 if (item != null) { 669 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 670 } 671 setPanoramaShareIntent(currentData.getContentUri()); 672 } 673 if (standardShare) { 674 if (!panoramaShare) { 675 MenuItem item = mActionBarMenu.findItem(R.id.action_share); 676 if (item != null) { 677 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 678 item.setTitle(getResources().getString(R.string.share)); 679 } 680 } 681 setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType()); 682 setNfcBeamPushUri(currentData.getContentUri()); 683 } 684 685 boolean itemHasLocation = currentData.getLatLong() != null; 686 setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map, 687 itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0); 688 } 689 690 private void setMenuItemVisible(Menu menu, int itemId, boolean visible) { 691 MenuItem item = menu.findItem(itemId); 692 if (item != null) { 693 item.setVisible(visible); 694 } 695 } 696 697 private final ImageTaskManager.TaskListener mPlaceholderListener = 698 new ImageTaskManager.TaskListener() { 699 700 @Override 701 public void onTaskQueued(String filePath, final Uri imageUri) { 702 mMainHandler.post(new Runnable() { 703 @Override 704 public void run() { 705 notifyNewMedia(imageUri); 706 int dataID = mDataAdapter.findDataByContentUri(imageUri); 707 if (dataID != -1) { 708 LocalData d = mDataAdapter.getLocalData(dataID); 709 InProgressDataWrapper newData = new InProgressDataWrapper(d, true); 710 mDataAdapter.updateData(dataID, newData); 711 } 712 } 713 }); 714 } 715 716 @Override 717 public void onTaskDone(String filePath, final Uri imageUri) { 718 mMainHandler.post(new Runnable() { 719 @Override 720 public void run() { 721 mDataAdapter.refresh(getContentResolver(), imageUri); 722 } 723 }); 724 } 725 726 @Override 727 public void onTaskProgress(String filePath, Uri imageUri, int progress) { 728 // Do nothing 729 } 730 }; 731 732 private final ImageTaskManager.TaskListener mStitchingListener = 733 new ImageTaskManager.TaskListener() { 734 @Override 735 public void onTaskQueued(String filePath, final Uri imageUri) { 736 mMainHandler.post(new Runnable() { 737 @Override 738 public void run() { 739 notifyNewMedia(imageUri); 740 int dataID = mDataAdapter.findDataByContentUri(imageUri); 741 if (dataID != -1) { 742 // Don't allow special UI actions (swipe to 743 // delete, for example) on in-progress data. 744 LocalData d = mDataAdapter.getLocalData(dataID); 745 InProgressDataWrapper newData = new InProgressDataWrapper(d); 746 mDataAdapter.updateData(dataID, newData); 747 } 748 } 749 }); 750 } 751 752 @Override 753 public void onTaskDone(String filePath, final Uri imageUri) { 754 Log.v(TAG, "onTaskDone:" + filePath); 755 mMainHandler.post(new Runnable() { 756 @Override 757 public void run() { 758 int doneID = mDataAdapter.findDataByContentUri(imageUri); 759 int currentDataId = mFilmstripController.getCurrentId(); 760 761 if (currentDataId == doneID) { 762 hidePanoStitchingProgress(); 763 updateStitchingProgress(0); 764 } 765 766 mDataAdapter.refresh(getContentResolver(), imageUri); 767 } 768 }); 769 } 770 771 @Override 772 public void onTaskProgress( 773 String filePath, final Uri imageUri, final int progress) { 774 mMainHandler.post(new Runnable() { 775 @Override 776 public void run() { 777 int currentDataId = mFilmstripController.getCurrentId(); 778 if (currentDataId == -1) { 779 return; 780 } 781 if (imageUri.equals( 782 mDataAdapter.getLocalData(currentDataId).getContentUri())) { 783 updateStitchingProgress(progress); 784 } 785 } 786 }); 787 } 788 }; 789 790 @Override 791 public Context getAndroidContext() { 792 return this; 793 } 794 795 @Override 796 public int getCurrentModuleIndex() { 797 return mCurrentModeIndex; 798 } 799 800 @Override 801 public SurfaceTexture getPreviewBuffer() { 802 // TODO: implement this 803 return null; 804 } 805 806 @Override 807 public void onPreviewStarted() { 808 mCameraAppUI.onPreviewStarted(); 809 } 810 811 @Override 812 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 813 mCameraAppUI.setPreviewStatusListener(previewStatusListener); 814 } 815 816 @Override 817 public FrameLayout getModuleLayoutRoot() { 818 return mCameraAppUI.getModuleRootView(); 819 } 820 821 @Override 822 public void setShutterEventsListener(ShutterEventsListener listener) { 823 // TODO: implement this 824 } 825 826 @Override 827 public void setShutterEnabled(boolean enabled) { 828 // TODO: implement this 829 } 830 831 @Override 832 public boolean isShutterEnabled() { 833 // TODO: implement this 834 return false; 835 } 836 837 @Override 838 public void startPreCaptureAnimation() { 839 // TODO: implement this 840 } 841 842 @Override 843 public void cancelPreCaptureAnimation() { 844 // TODO: implement this 845 } 846 847 @Override 848 public void startPostCaptureAnimation() { 849 // TODO: implement this 850 } 851 852 @Override 853 public void startPostCaptureAnimation(Bitmap thumbnail) { 854 // TODO: implement this 855 } 856 857 @Override 858 public void cancelPostCaptureAnimation() { 859 // TODO: implement this 860 } 861 862 @Override 863 public OrientationManager getOrientationManager() { 864 return mOrientationManager; 865 } 866 867 @Override 868 public LocationManager getLocationManager() { 869 return mLocationManager; 870 } 871 872 @Override 873 public void lockOrientation() { 874 mOrientationManager.lockOrientation(); 875 } 876 877 @Override 878 public void unlockOrientation() { 879 mOrientationManager.unlockOrientation(); 880 } 881 882 @Override 883 public void notifyNewMedia(Uri uri) { 884 ContentResolver cr = getContentResolver(); 885 String mimeType = cr.getType(uri); 886 if (mimeType.startsWith("video/")) { 887 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 888 mDataAdapter.addNewVideo(cr, uri); 889 } else if (mimeType.startsWith("image/")) { 890 CameraUtil.broadcastNewPicture(this, uri); 891 mDataAdapter.addNewPhoto(cr, uri); 892 } else if (mimeType.startsWith("application/stitching-preview")) { 893 mDataAdapter.addNewPhoto(cr, uri); 894 } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) { 895 mDataAdapter.addNewPhoto(cr, uri); 896 } else { 897 android.util.Log.w(TAG, "Unknown new media with MIME type:" 898 + mimeType + ", uri:" + uri); 899 } 900 } 901 902 @Override 903 public void enableKeepScreenOn(boolean enabled) { 904 if (mPaused) { 905 return; 906 } 907 908 mKeepScreenOn = enabled; 909 if (mKeepScreenOn) { 910 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 911 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 912 } else { 913 keepScreenOnForAWhile(); 914 } 915 } 916 917 @Override 918 public CameraProvider getCameraProvider() { 919 return mCameraController; 920 } 921 922 private void removeData(int dataID) { 923 mDataAdapter.removeData(CameraActivity.this, dataID); 924 if (mDataAdapter.getTotalNumber() > 1) { 925 showUndoDeletionBar(); 926 } else { 927 // If camera preview is the only view left in filmstrip, 928 // no need to show undo bar. 929 mPendingDeletion = true; 930 performDeletion(); 931 } 932 } 933 934 935 @Override 936 public boolean onCreateOptionsMenu(Menu menu) { 937 // Inflate the menu items for use in the action bar 938 MenuInflater inflater = getMenuInflater(); 939 inflater.inflate(R.menu.operations, menu); 940 mActionBarMenu = menu; 941 942 // Configure the standard share action provider 943 MenuItem item = menu.findItem(R.id.action_share); 944 mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider(); 945 mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml"); 946 if (mStandardShareIntent != null) { 947 mStandardShareActionProvider.setShareIntent(mStandardShareIntent); 948 } 949 950 // Configure the panorama share action provider 951 item = menu.findItem(R.id.action_share_panorama); 952 mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider(); 953 mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml"); 954 if (mPanoramaShareIntent != null) { 955 mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); 956 } 957 958 mStandardShareActionProvider.setOnShareTargetSelectedListener(this); 959 mPanoramaShareActionProvider.setOnShareTargetSelectedListener(this); 960 961 return super.onCreateOptionsMenu(menu); 962 } 963 964 @Override 965 public boolean onOptionsItemSelected(MenuItem item) { 966 int currentDataId = mFilmstripController.getCurrentId(); 967 if (currentDataId < 0) { 968 return false; 969 } 970 final LocalData localData = mDataAdapter.getLocalData(currentDataId); 971 972 // Handle presses on the action bar items 973 switch (item.getItemId()) { 974 case android.R.id.home: 975 onBackPressed(); 976 return true; 977 case R.id.action_delete: 978 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 979 UsageStatistics.ACTION_DELETE, null, 0, 980 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); 981 removeData(currentDataId); 982 return true; 983 case R.id.action_edit: 984 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 985 UsageStatistics.ACTION_EDIT, null, 0, 986 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); 987 launchEditor(localData); 988 return true; 989 case R.id.action_trim: { 990 // This is going to be handled by the Gallery app. 991 Intent intent = new Intent(ACTION_TRIM_VIDEO); 992 LocalData currentData = mDataAdapter.getLocalData(mFilmstripController.getCurrentId()); 993 intent.setData(currentData.getContentUri()); 994 // We need the file path to wrap this into a RandomAccessFile. 995 intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath()); 996 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 997 return true; 998 } 999 case R.id.action_rotate_ccw: 1000 localData.rotate90Degrees(this, mDataAdapter, currentDataId, false); 1001 return true; 1002 case R.id.action_rotate_cw: 1003 localData.rotate90Degrees(this, mDataAdapter, currentDataId, true); 1004 return true; 1005 case R.id.action_crop: { 1006 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 1007 UsageStatistics.ACTION_CROP, null, 0, 1008 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); 1009 Intent intent = new Intent(CropActivity.CROP_ACTION); 1010 intent.setClass(this, CropActivity.class); 1011 intent.setDataAndType(localData.getContentUri(), localData.getMimeType()) 1012 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1013 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 1014 return true; 1015 } 1016 case R.id.action_setas: { 1017 Intent intent = new Intent(Intent.ACTION_ATTACH_DATA) 1018 .setDataAndType(localData.getContentUri(), 1019 localData.getMimeType()) 1020 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1021 intent.putExtra("mimeType", intent.getType()); 1022 startActivityForResult(Intent.createChooser( 1023 intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW); 1024 return true; 1025 } 1026 case R.id.action_details: 1027 (new AsyncTask<Void, Void, MediaDetails>() { 1028 @Override 1029 protected MediaDetails doInBackground(Void... params) { 1030 return localData.getMediaDetails(CameraActivity.this); 1031 } 1032 1033 @Override 1034 protected void onPostExecute(MediaDetails mediaDetails) { 1035 if (mediaDetails != null) { 1036 DetailsDialog.create(CameraActivity.this, mediaDetails).show(); 1037 } 1038 } 1039 }).execute(); 1040 return true; 1041 case R.id.action_show_on_map: 1042 double[] latLong = localData.getLatLong(); 1043 if (latLong != null) { 1044 CameraUtil.showOnMap(this, latLong); 1045 } 1046 return true; 1047 default: 1048 return super.onOptionsItemSelected(item); 1049 } 1050 } 1051 1052 private boolean isCaptureIntent() { 1053 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 1054 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 1055 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1056 return true; 1057 } else { 1058 return false; 1059 } 1060 } 1061 1062 @Override 1063 public void onCreate(Bundle state) { 1064 super.onCreate(state); 1065 GcamHelper.init(getContentResolver()); 1066 1067 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 1068 setContentView(R.layout.activity_main); 1069 mActionBar = getActionBar(); 1070 mActionBar.addOnMenuVisibilityListener(this); 1071 mMainHandler = new MainHandler(getMainLooper()); 1072 mCameraController = 1073 new CameraController(this, this, mMainHandler, 1074 CameraManagerFactory.getAndroidCameraManager()); 1075 ComboPreferences prefs = new ComboPreferences(getAndroidContext()); 1076 1077 mSettingsManager = new SettingsManager(this, null, mCameraController.getNumberOfCameras()); 1078 // Remove this after we get rid of ComboPreferences. 1079 int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID)); 1080 prefs.setLocalId(this, cameraId); 1081 CameraSettings.upgradeGlobalPreferences(prefs, mCameraController.getNumberOfCameras()); 1082 // TODO: Try to move all the resources allocation to happen as soon as 1083 // possible so we can call module.init() at the earliest time. 1084 mModuleManager = new ModuleManagerImpl(); 1085 ModulesInfo.setupModules(this, mModuleManager); 1086 1087 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); 1088 mModeListView.init(mModuleManager.getSupportedModeIndexList()); 1089 if (ApiHelper.HAS_ROTATION_ANIMATION) { 1090 setRotationAnimation(); 1091 } 1092 1093 // Check if this is in the secure camera mode. 1094 Intent intent = getIntent(); 1095 String action = intent.getAction(); 1096 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 1097 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 1098 mSecureCamera = true; 1099 } else { 1100 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 1101 } 1102 1103 if (mSecureCamera) { 1104 // Change the window flags so that secure camera can show when locked 1105 Window win = getWindow(); 1106 WindowManager.LayoutParams params = win.getAttributes(); 1107 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 1108 win.setAttributes(params); 1109 1110 // Filter for screen off so that we can finish secure camera activity 1111 // when screen is off. 1112 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 1113 registerReceiver(mScreenOffReceiver, filter); 1114 } 1115 mCameraAppUI = new CameraAppUI(this, 1116 (MainActivityLayout) findViewById(R.id.activity_root_view), 1117 isSecureCamera(), isCaptureIntent()); 1118 1119 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener); 1120 1121 mAboveFilmstripControlLayout = 1122 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout); 1123 mPanoramaManager = AppManagerFactory.getInstance(this) 1124 .getPanoramaStitchingManager(); 1125 mPlaceholderManager = AppManagerFactory.getInstance(this) 1126 .getGcamProcessingManager(); 1127 mPanoramaManager.addTaskListener(mStitchingListener); 1128 mPlaceholderManager.addTaskListener(mPlaceholderListener); 1129 mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel); 1130 mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar); 1131 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController(); 1132 mFilmstripController.setImageGap( 1133 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 1134 mPanoramaViewHelper = new PanoramaViewHelper(this); 1135 mPanoramaViewHelper.onCreate(); 1136 // Set up the camera preview first so the preview shows up ASAP. 1137 mDataAdapter = new CameraDataAdapter( 1138 new ColorDrawable(getResources().getColor(R.color.photo_placeholder))); 1139 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener); 1140 1141 1142 mLocationManager = new LocationManager(this, 1143 new LocationManager.Listener() { 1144 @Override 1145 public void showGpsOnScreenIndicator(boolean hasSignal) { 1146 } 1147 1148 @Override 1149 public void hideGpsOnScreenIndicator() { 1150 } 1151 }); 1152 1153 mSettingsController = new SettingsController(this, mSettingsManager, mLocationManager); 1154 1155 int modeIndex = -1; 1156 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) 1157 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { 1158 modeIndex = ModeListView.MODE_VIDEO; 1159 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 1160 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 1161 .getAction())) { 1162 modeIndex = ModeListView.MODE_PHOTO; 1163 if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX) 1164 == ModeListView.MODE_GCAM && GcamHelper.hasGcamCapture()) { 1165 modeIndex = ModeListView.MODE_GCAM; 1166 } 1167 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 1168 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1169 modeIndex = ModeListView.MODE_PHOTO; 1170 } else { 1171 // If the activity has not been started using an explicit intent, 1172 // read the module index from the last time the user changed modes 1173 modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX); 1174 if ((modeIndex == ModeListView.MODE_GCAM && 1175 !GcamHelper.hasGcamCapture()) || modeIndex < 0) { 1176 modeIndex = ModeListView.MODE_PHOTO; 1177 } 1178 } 1179 1180 mOrientationManager = new OrientationManagerImpl(this); 1181 mOrientationManager.addOnOrientationChangeListener(mMainHandler, this); 1182 1183 setModuleFromModeIndex(modeIndex); 1184 1185 // TODO: Remove this when refactor is done. 1186 if (modeIndex == ModulesInfo.MODULE_PHOTO 1187 || modeIndex == ModulesInfo.MODULE_VIDEO 1188 || modeIndex == ModulesInfo.MODULE_GCAM) { 1189 mCameraAppUI.prepareModuleUI(); 1190 } 1191 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent()); 1192 1193 if (!mSecureCamera) { 1194 mFilmstripController.setDataAdapter(mDataAdapter); 1195 if (!isCaptureIntent()) { 1196 mDataAdapter.requestLoad(getContentResolver()); 1197 } 1198 } else { 1199 // Put a lock placeholder as the last image by setting its date to 1200 // 0. 1201 ImageView v = (ImageView) getLayoutInflater().inflate( 1202 R.layout.secure_album_placeholder, null); 1203 v.setOnClickListener(new View.OnClickListener() { 1204 @Override 1205 public void onClick(View view) { 1206 try { 1207 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 1208 UsageStatistics.ACTION_GALLERY, null); 1209 startActivity(IntentHelper.getGalleryIntent(CameraActivity.this)); 1210 } catch (ActivityNotFoundException e) { 1211 Log.w(TAG, "Failed to launch gallery activity, closing"); 1212 } 1213 finish(); 1214 } 1215 }); 1216 mDataAdapter = new FixedLastDataAdapter( 1217 mDataAdapter, 1218 new SimpleViewData( 1219 v, 1220 v.getDrawable().getIntrinsicWidth(), 1221 v.getDrawable().getIntrinsicHeight(), 1222 0, 0)); 1223 // Flush out all the original data. 1224 mDataAdapter.flush(); 1225 mFilmstripController.setDataAdapter(mDataAdapter); 1226 } 1227 1228 setupNfcBeamPush(); 1229 1230 mLocalImagesObserver = new LocalMediaObserver(); 1231 mLocalVideosObserver = new LocalMediaObserver(); 1232 1233 getContentResolver().registerContentObserver( 1234 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1235 mLocalImagesObserver); 1236 getContentResolver().registerContentObserver( 1237 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1238 mLocalVideosObserver); 1239 if (FeedbackHelper.feedbackAvailable()) { 1240 mFeedbackHelper = new FeedbackHelper(this); 1241 } 1242 } 1243 1244 private void setRotationAnimation() { 1245 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1246 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1247 Window win = getWindow(); 1248 WindowManager.LayoutParams winParams = win.getAttributes(); 1249 winParams.rotationAnimation = rotationAnimation; 1250 win.setAttributes(winParams); 1251 } 1252 1253 @Override 1254 public void onUserInteraction() { 1255 super.onUserInteraction(); 1256 if (!isFinishing()) { 1257 keepScreenOnForAWhile(); 1258 } 1259 } 1260 1261 @Override 1262 public boolean dispatchTouchEvent(MotionEvent ev) { 1263 boolean result = super.dispatchTouchEvent(ev); 1264 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1265 // Real deletion is postponed until the next user interaction after 1266 // the gesture that triggers deletion. Until real deletion is performed, 1267 // users can click the undo button to bring back the image that they 1268 // chose to delete. 1269 if (mPendingDeletion && !mIsUndoingDeletion) { 1270 performDeletion(); 1271 } 1272 } 1273 return result; 1274 } 1275 1276 @Override 1277 public void onPause() { 1278 mPaused = true; 1279 1280 // Delete photos that are pending deletion 1281 performDeletion(); 1282 mCurrentModule.pause(); 1283 mOrientationManager.pause(); 1284 // Close the camera and wait for the operation done. 1285 mCameraController.closeCamera(); 1286 1287 mLocalImagesObserver.setActivityPaused(true); 1288 mLocalVideosObserver.setActivityPaused(true); 1289 resetScreenOn(); 1290 super.onPause(); 1291 } 1292 1293 @Override 1294 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1295 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) { 1296 mResetToPreviewOnResume = false; 1297 } else { 1298 super.onActivityResult(requestCode, resultCode, data); 1299 } 1300 } 1301 1302 @Override 1303 public void onResume() { 1304 mPaused = false; 1305 1306 mLastLayoutOrientation = getResources().getConfiguration().orientation; 1307 1308 // TODO: Handle this in OrientationManager. 1309 // Auto-rotate off 1310 if (Settings.System.getInt(getContentResolver(), 1311 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 1312 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 1313 mAutoRotateScreen = false; 1314 } else { 1315 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 1316 mAutoRotateScreen = true; 1317 } 1318 1319 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 1320 UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName()); 1321 1322 mOrientationManager.resume(); 1323 super.onResume(); 1324 mCurrentModule.resume(); 1325 setSwipingEnabled(true); 1326 1327 if (mResetToPreviewOnResume) { 1328 mCameraAppUI.resume(); 1329 } 1330 // Default is showing the preview, unless disabled by explicitly 1331 // starting an activity we want to return from to the filmstrip rather 1332 // than the preview. 1333 mResetToPreviewOnResume = true; 1334 1335 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 1336 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 1337 if (!mSecureCamera) { 1338 // If it's secure camera, requestLoad() should not be called 1339 // as it will load all the data. 1340 if (!mFilmstripVisible) { 1341 mDataAdapter.requestLoad(getContentResolver()); 1342 } 1343 } 1344 } 1345 mLocalImagesObserver.setActivityPaused(false); 1346 mLocalVideosObserver.setActivityPaused(false); 1347 1348 keepScreenOnForAWhile(); 1349 } 1350 1351 @Override 1352 public void onStart() { 1353 super.onStart(); 1354 mPanoramaViewHelper.onStart(); 1355 } 1356 1357 @Override 1358 protected void onStop() { 1359 mPanoramaViewHelper.onStop(); 1360 if (mFeedbackHelper != null) { 1361 mFeedbackHelper.stopFeedback(); 1362 } 1363 1364 CameraManagerFactory.recycle(); 1365 super.onStop(); 1366 } 1367 1368 @Override 1369 public void onDestroy() { 1370 if (mSecureCamera) { 1371 unregisterReceiver(mScreenOffReceiver); 1372 } 1373 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 1374 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 1375 super.onDestroy(); 1376 } 1377 1378 @Override 1379 public void onConfigurationChanged(Configuration config) { 1380 super.onConfigurationChanged(config); 1381 Log.v(TAG, "onConfigurationChanged"); 1382 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 1383 return; 1384 } 1385 1386 if (mLastLayoutOrientation != config.orientation) { 1387 mLastLayoutOrientation = config.orientation; 1388 mCurrentModule.onLayoutOrientationChanged( 1389 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 1390 } 1391 } 1392 1393 @Override 1394 public boolean onKeyDown(int keyCode, KeyEvent event) { 1395 if (mFilmstripController.inCameraFullscreen()) { 1396 if (mCurrentModule.onKeyDown(keyCode, event)) { 1397 return true; 1398 } 1399 // Prevent software keyboard or voice search from showing up. 1400 if (keyCode == KeyEvent.KEYCODE_SEARCH 1401 || keyCode == KeyEvent.KEYCODE_MENU) { 1402 if (event.isLongPress()) { 1403 return true; 1404 } 1405 } 1406 } 1407 1408 return super.onKeyDown(keyCode, event); 1409 } 1410 1411 @Override 1412 public boolean onKeyUp(int keyCode, KeyEvent event) { 1413 if (mFilmstripController.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) { 1414 return true; 1415 } 1416 return super.onKeyUp(keyCode, event); 1417 } 1418 1419 @Override 1420 public void onBackPressed() { 1421 if (!mCameraAppUI.onBackPressed()) { 1422 if (!mCurrentModule.onBackPressed()) { 1423 super.onBackPressed(); 1424 } 1425 } 1426 } 1427 1428 public boolean isAutoRotateScreen() { 1429 return mAutoRotateScreen; 1430 } 1431 1432 protected void updateStorageSpace() { 1433 mStorageSpaceBytes = Storage.getAvailableSpace(); 1434 } 1435 1436 protected long getStorageSpaceBytes() { 1437 return mStorageSpaceBytes; 1438 } 1439 1440 protected void updateStorageSpaceAndHint() { 1441 updateStorageSpace(); 1442 updateStorageHint(mStorageSpaceBytes); 1443 } 1444 1445 protected void updateStorageHint(long storageSpace) { 1446 String message = null; 1447 if (storageSpace == Storage.UNAVAILABLE) { 1448 message = getString(R.string.no_storage); 1449 } else if (storageSpace == Storage.PREPARING) { 1450 message = getString(R.string.preparing_sd); 1451 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 1452 message = getString(R.string.access_sd_fail); 1453 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 1454 message = getString(R.string.spaceIsLow_content); 1455 } 1456 1457 if (message != null) { 1458 if (mStorageHint == null) { 1459 mStorageHint = OnScreenHint.makeText(this, message); 1460 } else { 1461 mStorageHint.setText(message); 1462 } 1463 mStorageHint.show(); 1464 } else if (mStorageHint != null) { 1465 mStorageHint.cancel(); 1466 mStorageHint = null; 1467 } 1468 } 1469 1470 protected void setResultEx(int resultCode) { 1471 mResultCodeForTesting = resultCode; 1472 setResult(resultCode); 1473 } 1474 1475 protected void setResultEx(int resultCode, Intent data) { 1476 mResultCodeForTesting = resultCode; 1477 mResultDataForTesting = data; 1478 setResult(resultCode, data); 1479 } 1480 1481 public int getResultCode() { 1482 return mResultCodeForTesting; 1483 } 1484 1485 public Intent getResultData() { 1486 return mResultDataForTesting; 1487 } 1488 1489 public boolean isSecureCamera() { 1490 return mSecureCamera; 1491 } 1492 1493 @Override 1494 public boolean isPaused() { 1495 return mPaused; 1496 } 1497 1498 @Override 1499 public void onModeSelected(int modeIndex) { 1500 if (mCurrentModeIndex == modeIndex) { 1501 return; 1502 } 1503 1504 if (modeIndex == ModeListView.MODE_SETTING) { 1505 onSettingsSelected(); 1506 return; 1507 } 1508 1509 CameraHolder.instance().keep(); 1510 closeModule(mCurrentModule); 1511 int oldModuleIndex = mCurrentModeIndex; 1512 setModuleFromModeIndex(modeIndex); 1513 1514 // TODO: The following check is temporary for modules attached to the 1515 // generic_module layout. When the refactor is done, similar logic will 1516 // be applied to all modules. 1517 if (mCurrentModeIndex == ModulesInfo.MODULE_PHOTO 1518 || mCurrentModeIndex == ModulesInfo.MODULE_VIDEO 1519 || mCurrentModeIndex == ModulesInfo.MODULE_GCAM) { 1520 if (oldModuleIndex != ModulesInfo.MODULE_PHOTO 1521 && oldModuleIndex != ModulesInfo.MODULE_VIDEO 1522 && oldModuleIndex != ModulesInfo.MODULE_GCAM) { 1523 mCameraAppUI.prepareModuleUI(); 1524 } else { 1525 mCameraAppUI.clearModuleUI(); 1526 } 1527 } else { 1528 // This is the old way of removing all views in CameraRootView. Will 1529 // be deprecated soon. It is here to make sure modules that haven't 1530 // been refactored can still function. 1531 mCameraAppUI.clearCameraUI(); 1532 } 1533 1534 openModule(mCurrentModule); 1535 mCurrentModule.onOrientationChanged(mLastRawOrientation); 1536 if (mMediaSaver != null) { 1537 mCurrentModule.onMediaSaverAvailable(mMediaSaver); 1538 } 1539 // Store the module index so we can use it the next time the Camera 1540 // starts up. 1541 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 1542 prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply(); 1543 } 1544 1545 public void onSettingsSelected() { 1546 // Temporary until we finalize the touch flow. 1547 LayoutInflater inflater = getLayoutInflater(); 1548 SettingsView settingsView = (SettingsView) inflater.inflate(R.layout.settings_list_layout, 1549 null, false); 1550 settingsView.setSettingsListener(mSettingsController); 1551 if (mFeedbackHelper != null) { 1552 settingsView.setFeedbackHelper(mFeedbackHelper); 1553 } 1554 PopupWindow popup = new PopupWindow(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 1555 popup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 1556 popup.setOutsideTouchable(true); 1557 popup.setFocusable(true); 1558 popup.setContentView(settingsView); 1559 popup.showAtLocation(mModeListView.getRootView(), Gravity.CENTER, 0, 0); 1560 } 1561 1562 /** 1563 * Sets the mCurrentModuleIndex, creates a new module instance for the given 1564 * index an sets it as mCurrentModule. 1565 */ 1566 private void setModuleFromModeIndex(int modeIndex) { 1567 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 1568 if (agent == null) { 1569 return; 1570 } 1571 if (!agent.requestAppForCamera()) { 1572 mCameraController.closeCamera(); 1573 } 1574 mCurrentModeIndex = agent.getModuleId(); 1575 mCurrentModule = (CameraModule) agent.createModule(this); 1576 } 1577 1578 @Override 1579 public SettingsManager getSettingsManager() { 1580 return mSettingsManager; 1581 } 1582 1583 @Override 1584 public CameraServices getServices() { 1585 return (CameraServices) getApplication(); 1586 } 1587 1588 @Override 1589 public SettingsController getSettingsController() { 1590 return mSettingsController; 1591 } 1592 1593 public ButtonManager getButtonManager() { 1594 if (mButtonManager == null) { 1595 mButtonManager = new ButtonManager(this); 1596 } 1597 return mButtonManager; 1598 } 1599 1600 /** 1601 * Creates an AlertDialog appropriate for choosing whether to enable location 1602 * on the first run of the app. 1603 */ 1604 public AlertDialog getFirstTimeLocationAlert() { 1605 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1606 builder = SettingsView.getFirstTimeLocationAlertBuilder(builder, mSettingsController); 1607 if (builder != null) { 1608 return builder.create(); 1609 } else { 1610 return null; 1611 } 1612 } 1613 1614 /** 1615 * Launches an ACTION_EDIT intent for the given local data item. 1616 */ 1617 public void launchEditor(LocalData data) { 1618 Intent intent = new Intent(Intent.ACTION_EDIT) 1619 .setDataAndType(data.getContentUri(), data.getMimeType()) 1620 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1621 try { 1622 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 1623 } catch (ActivityNotFoundException e) { 1624 startActivityForResult(Intent.createChooser(intent, null), 1625 REQ_CODE_DONT_SWITCH_TO_PREVIEW); 1626 } 1627 } 1628 1629 /** 1630 * Launch the tiny planet editor. 1631 * 1632 * @param data The data must be a 360 degree stereographically mapped 1633 * panoramic image. It will not be modified, instead a new item 1634 * with the result will be added to the filmstrip. 1635 */ 1636 public void launchTinyPlanetEditor(LocalData data) { 1637 TinyPlanetFragment fragment = new TinyPlanetFragment(); 1638 Bundle bundle = new Bundle(); 1639 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString()); 1640 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 1641 fragment.setArguments(bundle); 1642 fragment.show(getFragmentManager(), "tiny_planet"); 1643 } 1644 1645 private void openModule(CameraModule module) { 1646 module.init(this, isSecureCamera(), isCaptureIntent()); 1647 module.resume(); 1648 module.onPreviewVisibilityChanged(!mFilmstripVisible); 1649 } 1650 1651 private void closeModule(CameraModule module) { 1652 module.pause(); 1653 } 1654 1655 private void performDeletion() { 1656 if (!mPendingDeletion) { 1657 return; 1658 } 1659 hideUndoDeletionBar(false); 1660 mDataAdapter.executeDeletion(CameraActivity.this); 1661 1662 int currentId = mFilmstripController.getCurrentId(); 1663 updateActionBarMenu(currentId); 1664 } 1665 1666 public void showUndoDeletionBar() { 1667 if (mPendingDeletion) { 1668 performDeletion(); 1669 } 1670 Log.v(TAG, "showing undo bar"); 1671 mPendingDeletion = true; 1672 if (mUndoDeletionBar == null) { 1673 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 1674 mAboveFilmstripControlLayout, true); 1675 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 1676 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 1677 button.setOnClickListener(new View.OnClickListener() { 1678 @Override 1679 public void onClick(View view) { 1680 mDataAdapter.undoDataRemoval(); 1681 hideUndoDeletionBar(true); 1682 } 1683 }); 1684 // Setting undo bar clickable to avoid touch events going through 1685 // the bar to the buttons (eg. edit button, etc) underneath the bar. 1686 mUndoDeletionBar.setClickable(true); 1687 // When there is user interaction going on with the undo button, we 1688 // do not want to hide the undo bar. 1689 button.setOnTouchListener(new View.OnTouchListener() { 1690 @Override 1691 public boolean onTouch(View v, MotionEvent event) { 1692 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 1693 mIsUndoingDeletion = true; 1694 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 1695 mIsUndoingDeletion = false; 1696 } 1697 return false; 1698 } 1699 }); 1700 } 1701 mUndoDeletionBar.setAlpha(0f); 1702 mUndoDeletionBar.setVisibility(View.VISIBLE); 1703 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 1704 } 1705 1706 private void hideUndoDeletionBar(boolean withAnimation) { 1707 Log.v(TAG, "Hiding undo deletion bar"); 1708 mPendingDeletion = false; 1709 if (mUndoDeletionBar != null) { 1710 if (withAnimation) { 1711 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 1712 .setListener(new Animator.AnimatorListener() { 1713 @Override 1714 public void onAnimationStart(Animator animation) { 1715 // Do nothing. 1716 } 1717 1718 @Override 1719 public void onAnimationEnd(Animator animation) { 1720 mUndoDeletionBar.setVisibility(View.GONE); 1721 } 1722 1723 @Override 1724 public void onAnimationCancel(Animator animation) { 1725 // Do nothing. 1726 } 1727 1728 @Override 1729 public void onAnimationRepeat(Animator animation) { 1730 // Do nothing. 1731 } 1732 }).start(); 1733 } else { 1734 mUndoDeletionBar.setVisibility(View.GONE); 1735 } 1736 } 1737 } 1738 1739 @Override 1740 public void onOrientationChanged(int orientation) { 1741 // We keep the last known orientation. So if the user first orient 1742 // the camera then point the camera to floor or sky, we still have 1743 // the correct orientation. 1744 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) { 1745 return; 1746 } 1747 mLastRawOrientation = orientation; 1748 if (mCurrentModule != null) { 1749 mCurrentModule.onOrientationChanged(orientation); 1750 } 1751 } 1752 1753 /** 1754 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 1755 * capture intent. 1756 * 1757 * @param enable {@code true} to enable swipe. 1758 */ 1759 public void setSwipingEnabled(boolean enable) { 1760 // TODO: Bring back the functionality. 1761 if (isCaptureIntent()) { 1762 //lockPreview(true); 1763 } else { 1764 //lockPreview(!enable); 1765 } 1766 } 1767 1768 // Accessor methods for getting latency times used in performance testing 1769 public long getAutoFocusTime() { 1770 return (mCurrentModule instanceof PhotoModule) ? 1771 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 1772 } 1773 1774 public long getShutterLag() { 1775 return (mCurrentModule instanceof PhotoModule) ? 1776 ((PhotoModule) mCurrentModule).mShutterLag : -1; 1777 } 1778 1779 public long getShutterToPictureDisplayedTime() { 1780 return (mCurrentModule instanceof PhotoModule) ? 1781 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 1782 } 1783 1784 public long getPictureDisplayedToJpegCallbackTime() { 1785 return (mCurrentModule instanceof PhotoModule) ? 1786 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 1787 } 1788 1789 public long getJpegCallbackFinishTime() { 1790 return (mCurrentModule instanceof PhotoModule) ? 1791 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 1792 } 1793 1794 public long getCaptureStartTime() { 1795 return (mCurrentModule instanceof PhotoModule) ? 1796 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 1797 } 1798 1799 public boolean isRecording() { 1800 return (mCurrentModule instanceof VideoModule) ? 1801 ((VideoModule) mCurrentModule).isRecording() : false; 1802 } 1803 1804 public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() { 1805 return mCameraController; 1806 } 1807 1808 // For debugging purposes only. 1809 public CameraModule getCurrentModule() { 1810 return mCurrentModule; 1811 } 1812 1813 private void keepScreenOnForAWhile() { 1814 if (mKeepScreenOn) { 1815 return; 1816 } 1817 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 1818 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1819 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 1820 } 1821 1822 private void resetScreenOn() { 1823 mKeepScreenOn = false; 1824 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 1825 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1826 } 1827 1828 /** 1829 * Updates the visibility of the filmstrip bottom controls. 1830 */ 1831 private void updateUiByData(final int dataId) { 1832 if (isSecureCamera()) { 1833 // We cannot show buttons in secure camera since go to other 1834 // activities might create a security hole. 1835 return; 1836 } 1837 1838 final LocalData currentData = mDataAdapter.getLocalData(dataId); 1839 if (currentData == null) { 1840 Log.w(TAG, "Current data ID not found."); 1841 hidePanoStitchingProgress(); 1842 return; 1843 } 1844 1845 updateActionBarMenu(dataId); 1846 1847 /* Bottom controls. */ 1848 1849 final CameraAppUI.BottomControls filmstripBottomControls = 1850 mCameraAppUI.getFilmstripBottomControls(); 1851 // We can only edit photos, not videos. 1852 filmstripBottomControls.setEditButtonVisibility(currentData.isPhoto()); 1853 1854 /* Progress bar */ 1855 1856 final int panoStitchingProgress = 1857 mPanoramaManager.getTaskProgress(currentData.getContentUri()); 1858 if (panoStitchingProgress < 0) { 1859 hidePanoStitchingProgress(); 1860 } else { 1861 showPanoStitchingProgress(); 1862 updateStitchingProgress(panoStitchingProgress); 1863 } 1864 1865 /* View button */ 1866 1867 // We need to add this to a separate DB. 1868 // TODO: Redesign this. 1869 currentData.requestAuxInfo(this, new LocalData.AuxInfoSupportCallback() { 1870 @Override 1871 public void auxInfoAvailable(final boolean isPanorama, 1872 final boolean isPanorama360, boolean isRgbz) { 1873 // Make sure the returned data is for the current image. 1874 if (dataId != mFilmstripController.getCurrentId()) { 1875 return; 1876 } 1877 1878 // If this is a photo sphere, show the button to view it. If it's a full 1879 // 360 photo sphere, show the tiny planet button. 1880 final int viewButtonVisibility; 1881 if (isPanorama) { 1882 viewButtonVisibility = CameraAppUI.BottomControls.VIEW_PHOTO_SPHERE; 1883 } else if (isRgbz) { 1884 viewButtonVisibility = CameraAppUI.BottomControls.VIEW_RGBZ; 1885 } else { 1886 viewButtonVisibility = CameraAppUI.BottomControls.VIEW_NONE; 1887 } 1888 1889 runOnUiThread(new Runnable() { 1890 1891 @Override 1892 public void run() { 1893 if (mFilmstripController.getCurrentId() == dataId) { 1894 filmstripBottomControls.setTinyPlanetButtonVisibility(isPanorama360); 1895 filmstripBottomControls.setViewButtonVisibility(viewButtonVisibility); 1896 } 1897 } 1898 }); 1899 } 1900 }); 1901 } 1902} 1903