CameraActivity.java revision b65117fbce9a0dc6d7568a6b7b802baf5df6ead5
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.app.Dialog; 25import android.content.ActivityNotFoundException; 26import android.content.BroadcastReceiver; 27import android.content.ContentResolver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.pm.ActivityInfo; 32import android.content.pm.PackageManager; 33import android.content.res.Configuration; 34import android.graphics.Bitmap; 35import android.graphics.BitmapFactory; 36import android.graphics.Matrix; 37import android.graphics.Point; 38import android.graphics.SurfaceTexture; 39import android.graphics.drawable.Drawable; 40import android.hardware.Camera; 41import android.net.Uri; 42import android.nfc.NfcAdapter; 43import android.nfc.NfcAdapter.CreateBeamUrisCallback; 44import android.nfc.NfcEvent; 45import android.os.AsyncTask; 46import android.os.Build; 47import android.os.Bundle; 48import android.os.Handler; 49import android.os.HandlerThread; 50import android.os.Looper; 51import android.os.Message; 52import android.provider.MediaStore; 53import android.provider.Settings; 54import android.util.CameraPerformanceTracker; 55import android.view.ContextMenu; 56import android.view.ContextMenu.ContextMenuInfo; 57import android.view.KeyEvent; 58import android.view.Menu; 59import android.view.MenuInflater; 60import android.view.MenuItem; 61import android.view.MotionEvent; 62import android.view.View; 63import android.view.View.OnSystemUiVisibilityChangeListener; 64import android.view.ViewGroup; 65import android.view.Window; 66import android.view.WindowManager; 67import android.widget.FrameLayout; 68import android.widget.ImageView; 69import android.widget.ShareActionProvider; 70 71import com.android.camera.app.AppController; 72import com.android.camera.app.CameraAppUI; 73import com.android.camera.app.CameraController; 74import com.android.camera.cameradevice.CameraManager; 75import com.android.camera.cameradevice.CameraManagerFactory; 76import com.android.camera.app.CameraProvider; 77import com.android.camera.app.CameraServices; 78import com.android.camera.app.LocationManager; 79import com.android.camera.app.MemoryManager; 80import com.android.camera.app.MemoryQuery; 81import com.android.camera.app.ModuleManagerImpl; 82import com.android.camera.app.OrientationManager; 83import com.android.camera.app.OrientationManagerImpl; 84import com.android.camera.data.CameraDataAdapter; 85import com.android.camera.data.LocalDataViewType; 86import com.android.camera.data.FixedLastDataAdapter; 87import com.android.camera.data.LocalData; 88import com.android.camera.data.LocalDataAdapter; 89import com.android.camera.data.LocalDataUtil; 90import com.android.camera.data.LocalMediaData; 91import com.android.camera.data.LocalMediaObserver; 92import com.android.camera.data.LocalSessionData; 93import com.android.camera.data.MediaDetails; 94import com.android.camera.data.PanoramaMetadataLoader; 95import com.android.camera.data.RgbzMetadataLoader; 96import com.android.camera.data.SimpleViewData; 97import com.android.camera.debug.Log; 98import com.android.camera.filmstrip.FilmstripContentPanel; 99import com.android.camera.filmstrip.FilmstripController; 100import com.android.camera.hardware.HardwareSpec; 101import com.android.camera.hardware.HardwareSpecImpl; 102import com.android.camera.module.ModuleController; 103import com.android.camera.module.ModulesInfo; 104import com.android.camera.session.CaptureSession; 105import com.android.camera.session.CaptureSessionManager; 106import com.android.camera.session.CaptureSessionManager.SessionListener; 107import com.android.camera.settings.CameraSettingsActivity; 108import com.android.camera.settings.SettingsManager; 109import com.android.camera.settings.SettingsManager.SettingsCapabilities; 110import com.android.camera.settings.SettingsUtil; 111import com.android.camera.tinyplanet.TinyPlanetFragment; 112import com.android.camera.ui.AbstractTutorialOverlay; 113import com.android.camera.ui.DetailsDialog; 114import com.android.camera.ui.MainActivityLayout; 115import com.android.camera.ui.ModeListView; 116import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener; 117import com.android.camera.ui.PreviewStatusListener; 118import com.android.camera.util.ApiHelper; 119import com.android.camera.util.Callback; 120import com.android.camera.util.CameraUtil; 121import com.android.camera.util.FeedbackHelper; 122import com.android.camera.util.GalleryHelper; 123import com.android.camera.util.GcamHelper; 124import com.android.camera.util.IntentHelper; 125import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 126import com.android.camera.util.ReleaseDialogHelper; 127import com.android.camera.util.UsageStatistics; 128import com.android.camera.widget.FilmstripView; 129import com.android.camera.widget.Preloader; 130import com.android.camera2.R; 131import com.bumptech.glide.Glide; 132import com.bumptech.glide.resize.ImageManager; 133import com.google.common.logging.eventprotos; 134import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource; 135import com.google.common.logging.eventprotos.MediaInteraction; 136import com.google.common.logging.eventprotos.NavigationChange; 137 138import java.io.File; 139import java.io.FileInputStream; 140import java.io.FileNotFoundException; 141import java.lang.ref.WeakReference; 142import java.util.ArrayList; 143import java.util.HashMap; 144import java.util.List; 145import java.util.concurrent.Executors; 146 147public class CameraActivity extends Activity 148 implements AppController, CameraManager.CameraOpenCallback, 149 ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener, 150 OrientationManager.OnOrientationChangeListener { 151 152 private static final Log.Tag TAG = new Log.Tag("CameraActivity"); 153 154 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 155 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 156 public static final String ACTION_IMAGE_CAPTURE_SECURE = 157 "android.media.action.IMAGE_CAPTURE_SECURE"; 158 159 // The intent extra for camera from secure lock screen. True if the gallery 160 // should only show newly captured pictures. sSecureAlbumId does not 161 // increment. This is used when switching between camera, camcorder, and 162 // panorama. If the extra is not set, it is in the normal camera mode. 163 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 164 165 /** 166 * Request code from an activity we started that indicated that we do not 167 * want to reset the view to the preview in onResume. 168 */ 169 public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142; 170 171 public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999; 172 173 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2; 174 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins. 175 private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs. 176 /** Load metadata for 10 items ahead of our current. */ 177 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10; 178 179 /** Should be used wherever a context is needed. */ 180 private Context mAppContext; 181 182 /** 183 * Whether onResume should reset the view to the preview. 184 */ 185 private boolean mResetToPreviewOnResume = true; 186 187 /** 188 * This data adapter is used by FilmStripView. 189 */ 190 private LocalDataAdapter mDataAdapter; 191 192 private SettingsManager mSettingsManager; 193 private ModeListView mModeListView; 194 private boolean mModeListVisible = false; 195 private int mCurrentModeIndex; 196 private CameraModule mCurrentModule; 197 private ModuleManagerImpl mModuleManager; 198 private FrameLayout mAboveFilmstripControlLayout; 199 private FilmstripController mFilmstripController; 200 private boolean mFilmstripVisible; 201 /** Whether the filmstrip fully covers the preview. */ 202 private boolean mFilmstripCoversPreview = false; 203 private int mResultCodeForTesting; 204 private Intent mResultDataForTesting; 205 private OnScreenHint mStorageHint; 206 private final Object mStorageSpaceLock = new Object(); 207 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES; 208 private boolean mAutoRotateScreen; 209 private boolean mSecureCamera; 210 private int mLastRawOrientation; 211 private OrientationManagerImpl mOrientationManager; 212 private LocationManager mLocationManager; 213 private ButtonManager mButtonManager; 214 private Handler mMainHandler; 215 private PanoramaViewHelper mPanoramaViewHelper; 216 private ActionBar mActionBar; 217 private ViewGroup mUndoDeletionBar; 218 private boolean mIsUndoingDeletion = false; 219 220 private final Uri[] mNfcPushUris = new Uri[1]; 221 222 private LocalMediaObserver mLocalImagesObserver; 223 private LocalMediaObserver mLocalVideosObserver; 224 225 private boolean mPendingDeletion = false; 226 227 private CameraController mCameraController; 228 private boolean mPaused; 229 private CameraAppUI mCameraAppUI; 230 231 private PeekAnimationHandler mPeekAnimationHandler; 232 private HandlerThread mPeekAnimationThread; 233 234 private FeedbackHelper mFeedbackHelper; 235 236 private Intent mGalleryIntent; 237 private long mOnCreateTime; 238 239 private Menu mActionBarMenu; 240 private Preloader<Integer, AsyncTask> mPreloader; 241 242 private static final int LIGHTS_OUT_DELAY_MS = 4000; 243 private final int BASE_SYS_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 244 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 245 private final Runnable mLightsOutRunnable = new Runnable() { 246 @Override 247 public void run() { 248 getWindow().getDecorView().setSystemUiVisibility( 249 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE); 250 } 251 }; 252 private MemoryManager mMemoryManager; 253 254 @Override 255 public CameraAppUI getCameraAppUI() { 256 return mCameraAppUI; 257 } 258 259 // close activity when screen turns off 260 private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 261 @Override 262 public void onReceive(Context context, Intent intent) { 263 finish(); 264 } 265 }; 266 267 /** 268 * Whether the screen is kept turned on. 269 */ 270 private boolean mKeepScreenOn; 271 private int mLastLayoutOrientation; 272 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener = 273 new CameraAppUI.BottomPanel.Listener() { 274 275 /** 276 * If the current photo is a photo sphere, this will launch the 277 * Photo Sphere panorama viewer. 278 */ 279 @Override 280 public void onExternalViewer() { 281 if (mPanoramaViewHelper == null) { 282 return; 283 } 284 final LocalData data = getCurrentLocalData(); 285 if (data == null) { 286 return; 287 } 288 final Uri contentUri = data.getUri(); 289 if (contentUri == Uri.EMPTY) { 290 return; 291 } 292 293 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(data)) { 294 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri); 295 } else if (RgbzMetadataLoader.hasRGBZData(data)) { 296 mPanoramaViewHelper.showRgbz(contentUri); 297 if (mSettingsManager.getBoolean( 298 SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 299 mSettingsManager.setBoolean( 300 SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING, 301 false); 302 mCameraAppUI.clearClingForViewer( 303 CameraAppUI.BottomPanel.VIEWER_REFOCUS); 304 } 305 } 306 } 307 308 @Override 309 public void onEdit() { 310 LocalData data = getCurrentLocalData(); 311 if (data == null) { 312 return; 313 } 314 final int currentDataId = getCurrentDataId(); 315 UsageStatistics.instance().mediaInteraction(fileNameFromDataID(currentDataId), 316 MediaInteraction.InteractionType.EDIT, 317 NavigationChange.InteractionCause.BUTTON); 318 launchEditor(data); 319 } 320 321 @Override 322 public void onTinyPlanet() { 323 LocalData data = getCurrentLocalData(); 324 if (data == null) { 325 return; 326 } 327 launchTinyPlanetEditor(data); 328 } 329 330 @Override 331 public void onDelete() { 332 final int currentDataId = getCurrentDataId(); 333 UsageStatistics.instance().mediaInteraction(fileNameFromDataID(currentDataId), 334 MediaInteraction.InteractionType.DELETE, 335 NavigationChange.InteractionCause.BUTTON); 336 removeData(currentDataId); 337 } 338 339 @Override 340 public void onShare() { 341 final LocalData data = getCurrentLocalData(); 342 final int currentDataId = getCurrentDataId(); 343 UsageStatistics.instance().mediaInteraction(fileNameFromDataID(currentDataId), 344 MediaInteraction.InteractionType.SHARE, 345 NavigationChange.InteractionCause.BUTTON); 346 // If applicable, show release information before this item 347 // is shared. 348 if (PanoramaMetadataLoader.isPanorama(data) 349 || RgbzMetadataLoader.hasRGBZData(data)) { 350 ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this, 351 new Callback<Void>() { 352 @Override 353 public void onCallback(Void result) { 354 share(data); 355 } 356 }); 357 } else { 358 share(data); 359 } 360 } 361 362 private void share(LocalData data) { 363 Intent shareIntent = getShareIntentByData(data); 364 if (shareIntent != null) { 365 try { 366 launchActivityByIntent(shareIntent); 367 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false); 368 } catch (ActivityNotFoundException ex) { 369 // Nothing. 370 } 371 } 372 } 373 374 private int getCurrentDataId() { 375 return mFilmstripController.getCurrentId(); 376 } 377 378 private LocalData getCurrentLocalData() { 379 return mDataAdapter.getLocalData(getCurrentDataId()); 380 } 381 382 /** 383 * Sets up the share intent and NFC properly according to the 384 * data. 385 * 386 * @param data The data to be shared. 387 */ 388 private Intent getShareIntentByData(final LocalData data) { 389 Intent intent = null; 390 final Uri contentUri = data.getUri(); 391 if (PanoramaMetadataLoader.isPanorama360(data) && 392 data.getUri() != Uri.EMPTY) { 393 intent = new Intent(Intent.ACTION_SEND); 394 intent.setType("application/vnd.google.panorama360+jpg"); 395 intent.putExtra(Intent.EXTRA_STREAM, contentUri); 396 } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) { 397 final String mimeType = data.getMimeType(); 398 intent = getShareIntentFromType(mimeType); 399 if (intent != null) { 400 intent.putExtra(Intent.EXTRA_STREAM, contentUri); 401 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 402 } 403 intent = Intent.createChooser(intent, null); 404 } 405 return intent; 406 } 407 408 /** 409 * Get the share intent according to the mimeType 410 * 411 * @param mimeType The mimeType of current data. 412 * @return the video/image's ShareIntent or null if mimeType is 413 * invalid. 414 */ 415 private Intent getShareIntentFromType(String mimeType) { 416 // Lazily create the intent object. 417 Intent intent = new Intent(Intent.ACTION_SEND); 418 if (mimeType.startsWith("video/")) { 419 intent.setType("video/*"); 420 } else { 421 if (mimeType.startsWith("image/")) { 422 intent.setType("image/*"); 423 } else { 424 Log.w(TAG, "unsupported mimeType " + mimeType); 425 } 426 } 427 return intent; 428 } 429 430 @Override 431 public void onProgressErrorClicked() { 432 LocalData data = getCurrentLocalData(); 433 getServices().getCaptureSessionManager().removeErrorMessage( 434 data.getUri()); 435 updateBottomControlsByData(data); 436 } 437 }; 438 439 @Override 440 public void onCameraOpened(CameraManager.CameraProxy camera) { 441 /** 442 * The current UI requires that the flash option visibility in front-facing 443 * camera be 444 * * disabled if back facing camera supports flash 445 * * hidden if back facing camera does not support flash 446 * We save whether back facing camera supports flash because we cannot get 447 * this in front facing camera without a camera switch. 448 * 449 * If this preference is cleared, we also need to clear the camera facing 450 * setting so we default to opening the camera in back facing camera, and 451 * can save this flash support value again. 452 */ 453 if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) { 454 HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters()); 455 mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA, 456 hardware.isFlashSupported()); 457 } 458 459 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) { 460 // We shouldn't be here. Just close the camera and leave. 461 camera.release(false); 462 throw new IllegalStateException("Camera opened but the module shouldn't be " + 463 "requesting"); 464 } 465 if (mCurrentModule != null) { 466 SettingsCapabilities capabilities = 467 SettingsUtil.getSettingsCapabilities(camera); 468 mSettingsManager.changeCamera(camera.getCameraId(), capabilities); 469 resetExposureCompensationToDefault(camera); 470 mCurrentModule.onCameraAvailable(camera); 471 } 472 mCameraAppUI.onChangeCamera(); 473 } 474 475 private void resetExposureCompensationToDefault(CameraManager.CameraProxy camera) { 476 // Reset the exposure compensation before handing the camera to module. 477 Camera.Parameters parameters = camera.getParameters(); 478 parameters.setExposureCompensation(0); 479 camera.setParameters(parameters); 480 } 481 482 @Override 483 public void onCameraDisabled(int cameraId) { 484 UsageStatistics.instance().cameraFailure( 485 eventprotos.CameraFailure.FailureReason.SECURITY); 486 CameraUtil.showErrorAndFinish(this, 487 R.string.camera_disabled); 488 } 489 490 @Override 491 public void onDeviceOpenFailure(int cameraId, String info) { 492 // TODO: send "info" to UsageStatistics logging. 493 UsageStatistics.instance().cameraFailure( 494 eventprotos.CameraFailure.FailureReason.OPEN_FAILURE); 495 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera); 496 } 497 498 @Override 499 public void onDeviceOpenedAlready(int cameraId) { 500 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera); 501 } 502 503 @Override 504 public void onReconnectionFailure(CameraManager mgr) { 505 UsageStatistics.instance().cameraFailure( 506 eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE); 507 CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera); 508 } 509 510 private static class MainHandler extends Handler { 511 final WeakReference<CameraActivity> mActivity; 512 513 public MainHandler(CameraActivity activity, Looper looper) { 514 super(looper); 515 mActivity = new WeakReference<CameraActivity>(activity); 516 } 517 518 @Override 519 public void handleMessage(Message msg) { 520 CameraActivity activity = mActivity.get(); 521 if (activity == null) { 522 return; 523 } 524 switch (msg.what) { 525 526 case MSG_CLEAR_SCREEN_ON_FLAG: { 527 if (!activity.mPaused) { 528 activity.getWindow().clearFlags( 529 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 530 } 531 break; 532 } 533 } 534 } 535 } 536 537 private String fileNameFromDataID(int dataID) { 538 final LocalData localData = mDataAdapter.getLocalData(dataID); 539 540 File localFile = new File(localData.getPath()); 541 return localFile.getName(); 542 } 543 544 private final FilmstripContentPanel.Listener mFilmstripListener = 545 new FilmstripContentPanel.Listener() { 546 547 @Override 548 public void onSwipeOut() { 549 } 550 551 @Override 552 public void onSwipeOutBegin() { 553 mActionBar.hide(); 554 mFilmstripCoversPreview = false; 555 updatePreviewVisibility(); 556 } 557 558 @Override 559 public void onFilmstripHidden() { 560 mFilmstripVisible = false; 561 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 562 NavigationChange.InteractionCause.SWIPE_RIGHT); 563 CameraActivity.this.setFilmstripUiVisibility(false); 564 // When the user hide the filmstrip (either swipe out or 565 // tap on back key) we move to the first item so next time 566 // when the user swipe in the filmstrip, the most recent 567 // one is shown. 568 mFilmstripController.goToFirstItem(); 569 } 570 571 @Override 572 public void onFilmstripShown() { 573 mFilmstripVisible = true; 574 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 575 NavigationChange.InteractionCause.SWIPE_LEFT); 576 updateUiByData(mFilmstripController.getCurrentId()); 577 } 578 579 @Override 580 public void onFocusedDataLongPressed(int dataId) { 581 // Do nothing. 582 } 583 584 @Override 585 public void onFocusedDataPromoted(int dataID) { 586 UsageStatistics.instance().mediaInteraction(fileNameFromDataID(dataID), 587 MediaInteraction.InteractionType.DELETE, 588 NavigationChange.InteractionCause.SWIPE_UP); 589 removeData(dataID); 590 } 591 592 @Override 593 public void onFocusedDataDemoted(int dataID) { 594 UsageStatistics.instance().mediaInteraction(fileNameFromDataID(dataID), 595 MediaInteraction.InteractionType.DELETE, 596 NavigationChange.InteractionCause.SWIPE_DOWN); 597 removeData(dataID); 598 } 599 600 @Override 601 public void onEnterFullScreenUiShown(int dataId) { 602 if (mFilmstripVisible) { 603 CameraActivity.this.setFilmstripUiVisibility(true); 604 } 605 } 606 607 @Override 608 public void onLeaveFullScreenUiShown(int dataId) { 609 // Do nothing. 610 } 611 612 @Override 613 public void onEnterFullScreenUiHidden(int dataId) { 614 if (mFilmstripVisible) { 615 CameraActivity.this.setFilmstripUiVisibility(false); 616 } 617 } 618 619 @Override 620 public void onLeaveFullScreenUiHidden(int dataId) { 621 // Do nothing. 622 } 623 624 @Override 625 public void onEnterFilmstrip(int dataId) { 626 if (mFilmstripVisible) { 627 CameraActivity.this.setFilmstripUiVisibility(true); 628 } 629 } 630 631 @Override 632 public void onLeaveFilmstrip(int dataId) { 633 // Do nothing. 634 } 635 636 @Override 637 public void onDataReloaded() { 638 if (!mFilmstripVisible) { 639 return; 640 } 641 updateUiByData(mFilmstripController.getCurrentId()); 642 } 643 644 @Override 645 public void onDataUpdated(int dataId) { 646 if (!mFilmstripVisible) { 647 return; 648 } 649 updateUiByData(mFilmstripController.getCurrentId()); 650 } 651 652 @Override 653 public void onEnterZoomView(int dataID) { 654 if (mFilmstripVisible) { 655 CameraActivity.this.setFilmstripUiVisibility(false); 656 } 657 } 658 659 @Override 660 public void onDataFocusChanged(final int prevDataId, final int newDataId) { 661 if (!mFilmstripVisible) { 662 return; 663 } 664 // TODO: This callback is UI event callback, should always 665 // happen on UI thread. Find the reason for this 666 // runOnUiThread() and fix it. 667 runOnUiThread(new Runnable() { 668 @Override 669 public void run() { 670 updateUiByData(newDataId); 671 } 672 }); 673 } 674 675 @Override 676 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) { 677 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount); 678 } 679 }; 680 681 private final LocalDataAdapter.LocalDataListener mLocalDataListener = 682 new LocalDataAdapter.LocalDataListener() { 683 @Override 684 public void onMetadataUpdated(List<Integer> updatedData) { 685 int currentDataId = mFilmstripController.getCurrentId(); 686 for (Integer dataId : updatedData) { 687 if (dataId == currentDataId) { 688 updateBottomControlsByData(mDataAdapter.getLocalData(dataId)); 689 } 690 } 691 } 692 }; 693 694 public void gotoGallery() { 695 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP, 696 NavigationChange.InteractionCause.BUTTON); 697 698 mFilmstripController.goToNextItem(); 699 } 700 701 /** 702 * If 'visible' is false, this hides the action bar. Also maintains 703 * lights-out at all times. 704 * 705 * @param visible is false, this hides the action bar and filmstrip bottom 706 * controls. 707 */ 708 private void setFilmstripUiVisibility(boolean visible) { 709 mLightsOutRunnable.run(); 710 mCameraAppUI.getFilmstripBottomControls().setVisible(visible); 711 if (visible != mActionBar.isShowing()) { 712 if (visible) { 713 mActionBar.show(); 714 } else { 715 mActionBar.hide(); 716 } 717 } 718 mFilmstripCoversPreview = visible; 719 updatePreviewVisibility(); 720 } 721 722 private void hideSessionProgress() { 723 mCameraAppUI.getFilmstripBottomControls().hideProgress(); 724 } 725 726 private void showSessionProgress(CharSequence message) { 727 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls(); 728 controls.setProgressText(message); 729 controls.hideControls(); 730 controls.hideProgressError(); 731 controls.showProgress(); 732 } 733 734 private void showProcessError(CharSequence message) { 735 mCameraAppUI.getFilmstripBottomControls().showProgressError(message); 736 } 737 738 private void updateSessionProgress(int progress) { 739 mCameraAppUI.getFilmstripBottomControls().setProgress(progress); 740 } 741 742 private void updateSessionProgressText(CharSequence message) { 743 mCameraAppUI.getFilmstripBottomControls().setProgressText(message); 744 } 745 746 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 747 private void setupNfcBeamPush() { 748 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext); 749 if (adapter == null) { 750 return; 751 } 752 753 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 754 // Disable beaming 755 adapter.setNdefPushMessage(null, CameraActivity.this); 756 return; 757 } 758 759 adapter.setBeamPushUris(null, CameraActivity.this); 760 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { 761 @Override 762 public Uri[] createBeamUris(NfcEvent event) { 763 return mNfcPushUris; 764 } 765 }, CameraActivity.this); 766 } 767 768 @Override 769 public void onMenuVisibilityChanged(boolean isVisible) { 770 // TODO: Remove this or bring back the original implementation: cancel 771 // auto-hide actionbar. 772 } 773 774 @Override 775 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) { 776 int currentDataId = mFilmstripController.getCurrentId(); 777 if (currentDataId < 0) { 778 return false; 779 } 780 UsageStatistics.instance().mediaInteraction(fileNameFromDataID(currentDataId), 781 MediaInteraction.InteractionType.SHARE, 782 NavigationChange.InteractionCause.BUTTON); 783 // TODO add intent.getComponent().getPackageName() 784 return true; 785 } 786 787 // Note: All callbacks come back on the main thread. 788 private final SessionListener mSessionListener = 789 new SessionListener() { 790 @Override 791 public void onSessionQueued(final Uri uri) { 792 if (!Storage.isSessionUri(uri)) { 793 return; 794 } 795 LocalSessionData newData = new LocalSessionData(uri); 796 mDataAdapter.addData(newData); 797 } 798 799 @Override 800 public void onSessionDone(final Uri sessionUri) { 801 Log.v(TAG, "onSessionDone:" + sessionUri); 802 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri); 803 if (contentUri == null) { 804 mDataAdapter.refresh(sessionUri); 805 return; 806 } 807 LocalData newData = LocalMediaData.PhotoData.fromContentUri( 808 getContentResolver(), contentUri); 809 810 final int pos = mDataAdapter.findDataByContentUri(sessionUri); 811 if (pos == -1) { 812 // We do not have a placeholder for this image, perhaps 813 // due to the activity crashing or being killed. 814 mDataAdapter.addData(newData); 815 } else { 816 mDataAdapter.updateData(pos, newData); 817 } 818 } 819 820 @Override 821 public void onSessionProgress(final Uri uri, final int progress) { 822 if (progress < 0) { 823 // Do nothing, there is no task for this URI. 824 return; 825 } 826 int currentDataId = mFilmstripController.getCurrentId(); 827 if (currentDataId == -1) { 828 return; 829 } 830 if (uri.equals( 831 mDataAdapter.getLocalData(currentDataId).getUri())) { 832 updateSessionProgress(progress); 833 } 834 } 835 836 @Override 837 public void onSessionProgressText(final Uri uri, final CharSequence message) { 838 int currentDataId = mFilmstripController.getCurrentId(); 839 if (currentDataId == -1) { 840 return; 841 } 842 if (uri.equals( 843 mDataAdapter.getLocalData(currentDataId).getUri())) { 844 updateSessionProgressText(message); 845 } 846 } 847 848 @Override 849 public void onSessionUpdated(Uri uri) { 850 mDataAdapter.refresh(uri); 851 } 852 853 @Override 854 public void onSessionPreviewAvailable(Uri uri) { 855 mDataAdapter.refresh(uri); 856 int dataId = mDataAdapter.findDataByContentUri(uri); 857 if (dataId != -1) { 858 startPeekAnimation(mDataAdapter.getLocalData(dataId)); 859 } 860 } 861 862 @Override 863 public void onSessionFailed(Uri uri, CharSequence reason) { 864 Log.v(TAG, "onSessionFailed:" + uri); 865 866 int failedDataId = mDataAdapter.findDataByContentUri(uri); 867 int currentDataId = mFilmstripController.getCurrentId(); 868 869 if (currentDataId == failedDataId) { 870 updateSessionProgress(0); 871 showProcessError(reason); 872 } 873 // HERE 874 mDataAdapter.refresh(uri); 875 } 876 }; 877 878 @Override 879 public Context getAndroidContext() { 880 return mAppContext; 881 } 882 883 @Override 884 public void launchActivityByIntent(Intent intent) { 885 startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); 886 } 887 888 @Override 889 public int getCurrentModuleIndex() { 890 return mCurrentModeIndex; 891 } 892 893 @Override 894 public ModuleController getCurrentModuleController() { 895 return mCurrentModule; 896 } 897 898 @Override 899 public int getQuickSwitchToModuleId(int currentModuleIndex) { 900 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager, 901 mAppContext); 902 } 903 904 @Override 905 public SurfaceTexture getPreviewBuffer() { 906 // TODO: implement this 907 return null; 908 } 909 910 @Override 911 public void onPreviewReadyToStart() { 912 mCameraAppUI.onPreviewReadyToStart(); 913 } 914 915 @Override 916 public void onPreviewStarted() { 917 mCameraAppUI.onPreviewStarted(); 918 } 919 920 @Override 921 public void addPreviewAreaSizeChangedListener( 922 PreviewStatusListener.PreviewAreaChangedListener listener) { 923 mCameraAppUI.addPreviewAreaChangedListener(listener); 924 } 925 926 @Override 927 public void removePreviewAreaSizeChangedListener( 928 PreviewStatusListener.PreviewAreaChangedListener listener) { 929 mCameraAppUI.removePreviewAreaChangedListener(listener); 930 } 931 932 @Override 933 public void setupOneShotPreviewListener() { 934 mCameraController.setOneShotPreviewCallback(mMainHandler, 935 new CameraManager.CameraPreviewDataCallback() { 936 @Override 937 public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) { 938 mCurrentModule.onPreviewInitialDataReceived(); 939 mCameraAppUI.onNewPreviewFrame(); 940 } 941 } 942 ); 943 } 944 945 @Override 946 public void updatePreviewAspectRatio(float aspectRatio) { 947 mCameraAppUI.updatePreviewAspectRatio(aspectRatio); 948 } 949 950 @Override 951 public void updatePreviewTransform(Matrix matrix) { 952 mCameraAppUI.updatePreviewTransform(matrix); 953 } 954 955 @Override 956 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 957 mCameraAppUI.setPreviewStatusListener(previewStatusListener); 958 } 959 960 @Override 961 public FrameLayout getModuleLayoutRoot() { 962 return mCameraAppUI.getModuleRootView(); 963 } 964 965 @Override 966 public void setShutterEventsListener(ShutterEventsListener listener) { 967 // TODO: implement this 968 } 969 970 @Override 971 public void setShutterEnabled(boolean enabled) { 972 mCameraAppUI.setShutterButtonEnabled(enabled); 973 } 974 975 @Override 976 public boolean isShutterEnabled() { 977 return mCameraAppUI.isShutterButtonEnabled(); 978 } 979 980 @Override 981 public void startPreCaptureAnimation() { 982 mCameraAppUI.startPreCaptureAnimation(); 983 } 984 985 @Override 986 public void cancelPreCaptureAnimation() { 987 // TODO: implement this 988 } 989 990 @Override 991 public void startPostCaptureAnimation() { 992 // TODO: implement this 993 } 994 995 @Override 996 public void startPostCaptureAnimation(Bitmap thumbnail) { 997 // TODO: implement this 998 } 999 1000 @Override 1001 public void cancelPostCaptureAnimation() { 1002 // TODO: implement this 1003 } 1004 1005 @Override 1006 public OrientationManager getOrientationManager() { 1007 return mOrientationManager; 1008 } 1009 1010 @Override 1011 public LocationManager getLocationManager() { 1012 return mLocationManager; 1013 } 1014 1015 @Override 1016 public void lockOrientation() { 1017 if (mOrientationManager != null) { 1018 mOrientationManager.lockOrientation(); 1019 } 1020 } 1021 1022 @Override 1023 public void unlockOrientation() { 1024 if (mOrientationManager != null) { 1025 mOrientationManager.unlockOrientation(); 1026 } 1027 } 1028 1029 /** 1030 * Starts the filmstrip peek animation if the filmstrip is not visible. 1031 * Only {@link LocalData#LOCAL_IMAGE}, {@link 1032 * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link 1033 * LocalData#LOCAL_VIDEO} are supported. 1034 * 1035 * @param data The data to peek. 1036 */ 1037 private void startPeekAnimation(final LocalData data) { 1038 if (mFilmstripVisible || mPeekAnimationHandler == null) { 1039 return; 1040 } 1041 1042 int dataType = data.getLocalDataType(); 1043 if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA && 1044 dataType != LocalData.LOCAL_VIDEO) { 1045 return; 1046 } 1047 1048 mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() { 1049 @Override 1050 public void onCallback(Bitmap result) { 1051 mCameraAppUI.startPeekAnimation(result, true); 1052 } 1053 }); 1054 } 1055 1056 @Override 1057 public void notifyNewMedia(Uri uri) { 1058 ContentResolver cr = getContentResolver(); 1059 String mimeType = cr.getType(uri); 1060 if (LocalDataUtil.isMimeTypeVideo(mimeType)) { 1061 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 1062 LocalData newData = LocalMediaData.VideoData.fromContentUri(getContentResolver(), uri); 1063 if (newData == null) { 1064 Log.e(TAG, "Can't find video data in content resolver:" + uri); 1065 return; 1066 } 1067 if (mDataAdapter.addData(newData)) { 1068 startPeekAnimation(newData); 1069 } 1070 } else if (LocalDataUtil.isMimeTypeImage(mimeType)) { 1071 CameraUtil.broadcastNewPicture(mAppContext, uri); 1072 LocalData newData = LocalMediaData.PhotoData.fromContentUri(getContentResolver(), uri); 1073 if (newData == null) { 1074 Log.e(TAG, "Can't find photo data in content resolver:" + uri); 1075 return; 1076 } 1077 if (mDataAdapter.addData(newData)) { 1078 startPeekAnimation(newData); 1079 } 1080 } else { 1081 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri); 1082 } 1083 } 1084 1085 @Override 1086 public void enableKeepScreenOn(boolean enabled) { 1087 if (mPaused) { 1088 return; 1089 } 1090 1091 mKeepScreenOn = enabled; 1092 if (mKeepScreenOn) { 1093 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 1094 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1095 } else { 1096 keepScreenOnForAWhile(); 1097 } 1098 } 1099 1100 @Override 1101 public CameraProvider getCameraProvider() { 1102 return mCameraController; 1103 } 1104 1105 private void removeData(int dataID) { 1106 mDataAdapter.removeData(dataID); 1107 if (mDataAdapter.getTotalNumber() > 1) { 1108 showUndoDeletionBar(); 1109 } else { 1110 // If camera preview is the only view left in filmstrip, 1111 // no need to show undo bar. 1112 mPendingDeletion = true; 1113 performDeletion(); 1114 if (mFilmstripVisible) { 1115 mCameraAppUI.getFilmstripContentPanel().animateHide(); 1116 } 1117 } 1118 } 1119 1120 @Override 1121 public boolean onOptionsItemSelected(MenuItem item) { 1122 // Handle presses on the action bar items 1123 switch (item.getItemId()) { 1124 case android.R.id.home: 1125 if (mFilmstripVisible && startGallery()) { 1126 return true; 1127 } 1128 onBackPressed(); 1129 return true; 1130 case R.id.action_details: 1131 showDetailsDialog(mFilmstripController.getCurrentId()); 1132 return true; 1133 default: 1134 return super.onOptionsItemSelected(item); 1135 } 1136 } 1137 1138 private boolean isCaptureIntent() { 1139 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 1140 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 1141 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1142 return true; 1143 } else { 1144 return false; 1145 } 1146 } 1147 1148 private final CameraManager.CameraExceptionCallback mCameraDefaultExceptionCallback 1149 = new CameraManager.CameraExceptionCallback() { 1150 @Override 1151 public void onCameraException(RuntimeException e) { 1152 Log.e(TAG, "Camera Exception", e); 1153 CameraUtil.showErrorAndFinish(CameraActivity.this, 1154 R.string.cannot_connect_camera); 1155 } 1156 }; 1157 1158 @Override 1159 public void onCreate(Bundle state) { 1160 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START); 1161 1162 super.onCreate(state); 1163 final Glide glide = Glide.get(); 1164 if (!glide.isImageManagerSet()) { 1165 // We load exclusively large images, so we want fewer threads to minimize jank. 1166 glide.setImageManager(new ImageManager.Builder(getApplicationContext()) 1167 .setResizeService(Executors.newSingleThreadExecutor())); 1168 } 1169 1170 mOnCreateTime = System.currentTimeMillis(); 1171 mAppContext = getApplicationContext(); 1172 mSettingsManager = new SettingsManager(mAppContext, this); 1173 GcamHelper.init(getContentResolver()); 1174 1175 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 1176 setContentView(R.layout.activity_main); 1177 mActionBar = getActionBar(); 1178 mActionBar.addOnMenuVisibilityListener(this); 1179 mMainHandler = new MainHandler(this, getMainLooper()); 1180 mCameraController = 1181 new CameraController(mAppContext, this, mMainHandler, 1182 CameraManagerFactory.getAndroidCameraManager()); 1183 mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback, 1184 mMainHandler); 1185 1186 // TODO: Try to move all the resources allocation to happen as soon as 1187 // possible so we can call module.init() at the earliest time. 1188 mModuleManager = new ModuleManagerImpl(); 1189 ModulesInfo.setupModules(mAppContext, mModuleManager); 1190 1191 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); 1192 mModeListView.init(mModuleManager.getSupportedModeIndexList()); 1193 if (ApiHelper.HAS_ROTATION_ANIMATION) { 1194 setRotationAnimation(); 1195 } 1196 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() { 1197 @Override 1198 public void onVisibilityChanged(boolean visible) { 1199 mModeListVisible = visible; 1200 updatePreviewVisibility(); 1201 } 1202 }); 1203 1204 // Check if this is in the secure camera mode. 1205 Intent intent = getIntent(); 1206 String action = intent.getAction(); 1207 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 1208 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 1209 mSecureCamera = true; 1210 } else { 1211 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 1212 } 1213 1214 if (mSecureCamera) { 1215 // Foreground event caused by lock screen startup. 1216 // It is necessary to log this in onCreate, to avoid the 1217 // onResume->onPause->onResume sequence. 1218 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE, 1219 currentUserInterfaceMode()); 1220 1221 // Change the window flags so that secure camera can show when 1222 // locked 1223 Window win = getWindow(); 1224 WindowManager.LayoutParams params = win.getAttributes(); 1225 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 1226 win.setAttributes(params); 1227 1228 // Filter for screen off so that we can finish secure camera 1229 // activity 1230 // when screen is off. 1231 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 1232 registerReceiver(mScreenOffReceiver, filter); 1233 } 1234 mCameraAppUI = new CameraAppUI(this, 1235 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent()); 1236 1237 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener); 1238 1239 mAboveFilmstripControlLayout = 1240 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout); 1241 1242 // Add the session listener so we can track the session progress 1243 // updates. 1244 getServices().getCaptureSessionManager().addSessionListener(mSessionListener); 1245 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController(); 1246 mFilmstripController.setImageGap( 1247 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 1248 mPanoramaViewHelper = new PanoramaViewHelper(this); 1249 mPanoramaViewHelper.onCreate(); 1250 // Set up the camera preview first so the preview shows up ASAP. 1251 mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder); 1252 mDataAdapter.setLocalDataListener(mLocalDataListener); 1253 1254 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter, 1255 mDataAdapter); 1256 1257 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener); 1258 if (mSettingsManager.getBoolean(SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 1259 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS); 1260 } 1261 1262 mLocationManager = new LocationManager(mAppContext); 1263 1264 mOrientationManager = new OrientationManagerImpl(this); 1265 mOrientationManager.addOnOrientationChangeListener(mMainHandler, this); 1266 1267 setModuleFromModeIndex(getModeIndex()); 1268 mCameraAppUI.prepareModuleUI(); 1269 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent()); 1270 1271 if (!mSecureCamera) { 1272 mFilmstripController.setDataAdapter(mDataAdapter); 1273 if (!isCaptureIntent()) { 1274 mDataAdapter.requestLoad(new Callback<Void>() { 1275 @Override 1276 public void onCallback(Void result) { 1277 fillTemporarySessions(); 1278 } 1279 }); 1280 } 1281 } else { 1282 // Put a lock placeholder as the last image by setting its date to 1283 // 0. 1284 ImageView v = (ImageView) getLayoutInflater().inflate( 1285 R.layout.secure_album_placeholder, null); 1286 v.setOnClickListener(new View.OnClickListener() { 1287 @Override 1288 public void onClick(View view) { 1289 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 1290 NavigationChange.InteractionCause.BUTTON); 1291 startGallery(); 1292 finish(); 1293 } 1294 }); 1295 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera)); 1296 mDataAdapter = new FixedLastDataAdapter( 1297 mAppContext, 1298 mDataAdapter, 1299 new SimpleViewData( 1300 v, 1301 LocalDataViewType.SECURE_ALBUM_PLACEHOLDER, 1302 v.getDrawable().getIntrinsicWidth(), 1303 v.getDrawable().getIntrinsicHeight(), 1304 0, 0)); 1305 // Flush out all the original data. 1306 mDataAdapter.flush(); 1307 mFilmstripController.setDataAdapter(mDataAdapter); 1308 } 1309 1310 setupNfcBeamPush(); 1311 1312 mLocalImagesObserver = new LocalMediaObserver(); 1313 mLocalVideosObserver = new LocalMediaObserver(); 1314 1315 getContentResolver().registerContentObserver( 1316 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1317 mLocalImagesObserver); 1318 getContentResolver().registerContentObserver( 1319 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1320 mLocalVideosObserver); 1321 if (FeedbackHelper.feedbackAvailable()) { 1322 mFeedbackHelper = new FeedbackHelper(mAppContext); 1323 } 1324 mMemoryManager = getServices().getMemoryManager(); 1325 1326 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1327 public void run() { 1328 HashMap memoryData = mMemoryManager.queryMemory(); 1329 UsageStatistics.instance().reportMemoryConsumed(memoryData, 1330 MemoryQuery.REPORT_LABEL_LAUNCH); 1331 } 1332 }); 1333 } 1334 1335 /** 1336 * Get the current mode index from the Intent or from persistent 1337 * settings. 1338 */ 1339 public int getModeIndex() { 1340 int modeIndex = -1; 1341 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo); 1342 int videoIndex = getResources().getInteger(R.integer.camera_mode_video); 1343 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1344 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) 1345 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { 1346 modeIndex = videoIndex; 1347 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 1348 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) { 1349 // TODO: synchronize mode options with photo module without losing 1350 // HDR+ preferences. 1351 modeIndex = photoIndex; 1352 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 1353 .getAction()) 1354 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1355 modeIndex = mSettingsManager.getInt( 1356 SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX); 1357 } else { 1358 // If the activity has not been started using an explicit intent, 1359 // read the module index from the last time the user changed modes 1360 modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX); 1361 if ((modeIndex == gcamIndex && 1362 !GcamHelper.hasGcamCapture()) || modeIndex < 0) { 1363 modeIndex = photoIndex; 1364 } 1365 } 1366 return modeIndex; 1367 } 1368 1369 /** 1370 * Call this whenever the mode drawer or filmstrip change the visibility 1371 * state. 1372 */ 1373 private void updatePreviewVisibility() { 1374 if (mCurrentModule == null) { 1375 return; 1376 } 1377 1378 int visibility = getPreviewVisibility(); 1379 updatePreviewRendering(visibility); 1380 updateCaptureControls(visibility); 1381 mCurrentModule.onPreviewVisibilityChanged(visibility); 1382 } 1383 1384 private void updateCaptureControls(int visibility) { 1385 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1386 mCameraAppUI.setIndicatorBottomBarWrapperVisible(false); 1387 } else { 1388 mCameraAppUI.setIndicatorBottomBarWrapperVisible(true); 1389 } 1390 } 1391 1392 private void updatePreviewRendering(int visibility) { 1393 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1394 mCameraAppUI.pausePreviewRendering(); 1395 } else { 1396 mCameraAppUI.resumePreviewRendering(); 1397 } 1398 } 1399 1400 private int getPreviewVisibility() { 1401 if (mFilmstripCoversPreview) { 1402 return ModuleController.VISIBILITY_HIDDEN; 1403 } else if (mModeListVisible){ 1404 return ModuleController.VISIBILITY_COVERED; 1405 } else { 1406 return ModuleController.VISIBILITY_VISIBLE; 1407 } 1408 } 1409 1410 private void setRotationAnimation() { 1411 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1412 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1413 Window win = getWindow(); 1414 WindowManager.LayoutParams winParams = win.getAttributes(); 1415 winParams.rotationAnimation = rotationAnimation; 1416 win.setAttributes(winParams); 1417 } 1418 1419 @Override 1420 public void onUserInteraction() { 1421 super.onUserInteraction(); 1422 if (!isFinishing()) { 1423 keepScreenOnForAWhile(); 1424 } 1425 } 1426 1427 @Override 1428 public boolean dispatchTouchEvent(MotionEvent ev) { 1429 boolean result = super.dispatchTouchEvent(ev); 1430 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1431 // Real deletion is postponed until the next user interaction after 1432 // the gesture that triggers deletion. Until real deletion is 1433 // performed, users can click the undo button to bring back the 1434 // image that they chose to delete. 1435 if (mPendingDeletion && !mIsUndoingDeletion) { 1436 performDeletion(); 1437 } 1438 } 1439 return result; 1440 } 1441 1442 @Override 1443 public void onPause() { 1444 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE); 1445 1446 /* 1447 * Save the last module index after all secure camera and icon launches, 1448 * not just on mode switches. 1449 * 1450 * Right now we exclude capture intents from this logic, because we also 1451 * ignore the cross-Activity recovery logic in onStart for capture intents. 1452 */ 1453 if (!isCaptureIntent()) { 1454 mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, 1455 mCurrentModeIndex); 1456 } 1457 1458 mPaused = true; 1459 mPeekAnimationHandler = null; 1460 mPeekAnimationThread.quitSafely(); 1461 mPeekAnimationThread = null; 1462 1463 // Delete photos that are pending deletion 1464 performDeletion(); 1465 mCurrentModule.pause(); 1466 mOrientationManager.pause(); 1467 // Close the camera and wait for the operation done. 1468 mCameraController.closeCamera(); 1469 mPanoramaViewHelper.onPause(); 1470 1471 mLocalImagesObserver.setForegroundChangeListener(null); 1472 mLocalImagesObserver.setActivityPaused(true); 1473 mLocalVideosObserver.setActivityPaused(true); 1474 mPreloader.cancelAllLoads(); 1475 resetScreenOn(); 1476 super.onPause(); 1477 } 1478 1479 @Override 1480 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1481 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) { 1482 mResetToPreviewOnResume = false; 1483 } else { 1484 super.onActivityResult(requestCode, resultCode, data); 1485 } 1486 } 1487 1488 @Override 1489 public void onResume() { 1490 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME); 1491 1492 mPaused = false; 1493 1494 mLastLayoutOrientation = getResources().getConfiguration().orientation; 1495 1496 // TODO: Handle this in OrientationManager. 1497 // Auto-rotate off 1498 if (Settings.System.getInt(getContentResolver(), 1499 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 1500 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 1501 mAutoRotateScreen = false; 1502 } else { 1503 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 1504 mAutoRotateScreen = true; 1505 } 1506 1507 String action = getIntent().getAction(); 1508 // Foreground event logging 1509 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) { 1510 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_VIDEO_CAPTURE, 1511 currentUserInterfaceMode()); 1512 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)) { 1513 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_IMAGE_CAPTURE, 1514 currentUserInterfaceMode()); 1515 } else if (MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) || 1516 INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) { 1517 // logged in onCreate() 1518 } else if (Intent.ACTION_MAIN.equals(action)) { 1519 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_MAIN, 1520 currentUserInterfaceMode()); 1521 } else { 1522 UsageStatistics.instance().foregrounded(ForegroundSource.UNKNOWN_SOURCE, 1523 currentUserInterfaceMode()); 1524 } 1525 1526 Drawable galleryLogo; 1527 if (mSecureCamera) { 1528 mGalleryIntent = null; 1529 galleryLogo = null; 1530 } else { 1531 mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext); 1532 galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent); 1533 } 1534 if (galleryLogo == null) { 1535 try { 1536 galleryLogo = getPackageManager().getActivityLogo(getComponentName()); 1537 } catch (PackageManager.NameNotFoundException e) { 1538 Log.e(TAG, "Can't get the activity logo"); 1539 } 1540 } 1541 if (mGalleryIntent != null) { 1542 mActionBar.setDisplayUseLogoEnabled(true); 1543 } 1544 mActionBar.setLogo(galleryLogo); 1545 mOrientationManager.resume(); 1546 super.onResume(); 1547 mPeekAnimationThread = new HandlerThread("Peek animation"); 1548 mPeekAnimationThread.start(); 1549 mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper()); 1550 1551 mCurrentModule.hardResetSettings(mSettingsManager); 1552 mCurrentModule.resume(); 1553 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 1554 NavigationChange.InteractionCause.BUTTON); 1555 setSwipingEnabled(true); 1556 1557 if (!mResetToPreviewOnResume) { 1558 LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId()); 1559 if (data != null) { 1560 mDataAdapter.refresh(data.getUri()); 1561 } 1562 } 1563 // The share button might be disabled to avoid double tapping. 1564 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true); 1565 // Default is showing the preview, unless disabled by explicitly 1566 // starting an activity we want to return from to the filmstrip rather 1567 // than the preview. 1568 mResetToPreviewOnResume = true; 1569 1570 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 1571 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 1572 if (!mSecureCamera) { 1573 // If it's secure camera, requestLoad() should not be called 1574 // as it will load all the data. 1575 if (!mFilmstripVisible) { 1576 mDataAdapter.requestLoad(new Callback<Void>() { 1577 @Override 1578 public void onCallback(Void result) { 1579 fillTemporarySessions(); 1580 } 1581 }); 1582 } else { 1583 mDataAdapter.requestLoadNewPhotos(); 1584 } 1585 } 1586 } 1587 mLocalImagesObserver.setActivityPaused(false); 1588 mLocalVideosObserver.setActivityPaused(false); 1589 if (!mSecureCamera) { 1590 mLocalImagesObserver.setForegroundChangeListener( 1591 new LocalMediaObserver.ChangeListener() { 1592 @Override 1593 public void onChange() { 1594 mDataAdapter.requestLoadNewPhotos(); 1595 } 1596 }); 1597 } 1598 1599 keepScreenOnForAWhile(); 1600 1601 // Lights-out mode at all times. 1602 final View rootView = findViewById(R.id.activity_root_view); 1603 mLightsOutRunnable.run(); 1604 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( 1605 new OnSystemUiVisibilityChangeListener() { 1606 @Override 1607 public void onSystemUiVisibilityChange(int visibility) { 1608 mMainHandler.removeCallbacks(mLightsOutRunnable); 1609 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS); 1610 } 1611 }); 1612 1613 mPanoramaViewHelper.onResume(); 1614 ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager); 1615 syncLocationManagerSetting(); 1616 1617 final int previewVisibility = getPreviewVisibility(); 1618 updatePreviewRendering(previewVisibility); 1619 } 1620 1621 private void fillTemporarySessions() { 1622 if (mSecureCamera) { 1623 return; 1624 } 1625 // There might be sessions still in flight (processed by our service). 1626 // Make sure they're added to the filmstrip. 1627 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener); 1628 } 1629 1630 @Override 1631 public void onStart() { 1632 super.onStart(); 1633 mPanoramaViewHelper.onStart(); 1634 1635 /* 1636 * If we're starting after launching a different Activity (lockscreen), 1637 * we need to use the last mode used in the other Activity, and 1638 * not the old one from this Activity. 1639 * 1640 * This needs to happen before CameraAppUI.resume() in order to set the 1641 * mode cover icon to the actual last mode used. 1642 * 1643 * Right now we exclude capture intents from this logic. 1644 */ 1645 int modeIndex = getModeIndex(); 1646 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) { 1647 onModeSelected(modeIndex); 1648 } 1649 1650 if (mResetToPreviewOnResume) { 1651 mCameraAppUI.resume(); 1652 mResetToPreviewOnResume = false; 1653 } 1654 } 1655 1656 @Override 1657 protected void onStop() { 1658 mPanoramaViewHelper.onStop(); 1659 if (mFeedbackHelper != null) { 1660 mFeedbackHelper.stopFeedback(); 1661 } 1662 1663 mLocationManager.disconnect(); 1664 super.onStop(); 1665 } 1666 1667 @Override 1668 public void onDestroy() { 1669 if (mSecureCamera) { 1670 unregisterReceiver(mScreenOffReceiver); 1671 } 1672 mActionBar.removeOnMenuVisibilityListener(this); 1673 mSettingsManager.removeAllListeners(); 1674 mCameraController.removeCallbackReceiver(); 1675 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 1676 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 1677 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener); 1678 mCameraAppUI.onDestroy(); 1679 mModeListView.setVisibilityChangedListener(null); 1680 mCameraController = null; 1681 mSettingsManager = null; 1682 mCameraAppUI = null; 1683 mOrientationManager = null; 1684 mButtonManager = null; 1685 CameraManagerFactory.recycle(); 1686 super.onDestroy(); 1687 } 1688 1689 @Override 1690 public void onConfigurationChanged(Configuration config) { 1691 super.onConfigurationChanged(config); 1692 Log.v(TAG, "onConfigurationChanged"); 1693 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 1694 return; 1695 } 1696 1697 if (mLastLayoutOrientation != config.orientation) { 1698 mLastLayoutOrientation = config.orientation; 1699 mCurrentModule.onLayoutOrientationChanged( 1700 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 1701 } 1702 } 1703 1704 @Override 1705 public boolean onKeyDown(int keyCode, KeyEvent event) { 1706 if (!mFilmstripVisible) { 1707 if (mCurrentModule.onKeyDown(keyCode, event)) { 1708 return true; 1709 } 1710 // Prevent software keyboard or voice search from showing up. 1711 if (keyCode == KeyEvent.KEYCODE_SEARCH 1712 || keyCode == KeyEvent.KEYCODE_MENU) { 1713 if (event.isLongPress()) { 1714 return true; 1715 } 1716 } 1717 } 1718 1719 return super.onKeyDown(keyCode, event); 1720 } 1721 1722 @Override 1723 public boolean onKeyUp(int keyCode, KeyEvent event) { 1724 if (!mFilmstripVisible) { 1725 // If a module is in the middle of capture, it should 1726 // consume the key event. 1727 if (mCurrentModule.onKeyUp(keyCode, event)) { 1728 return true; 1729 } else if (keyCode == KeyEvent.KEYCODE_MENU 1730 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 1731 // Let the mode list view consume the event. 1732 mModeListView.onMenuPressed(); 1733 return true; 1734 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 1735 mCameraAppUI.showFilmstrip(); 1736 return true; 1737 } 1738 } else { 1739 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 1740 mFilmstripController.goToNextItem(); 1741 return true; 1742 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 1743 boolean wentToPrevious = mFilmstripController.goToPreviousItem(); 1744 if (!wentToPrevious) { 1745 // at beginning of filmstrip, hide and go back to preview 1746 mCameraAppUI.hideFilmstrip(); 1747 } 1748 return true; 1749 } 1750 } 1751 return super.onKeyUp(keyCode, event); 1752 } 1753 1754 @Override 1755 public void onBackPressed() { 1756 if (!mCameraAppUI.onBackPressed()) { 1757 if (!mCurrentModule.onBackPressed()) { 1758 super.onBackPressed(); 1759 } 1760 } 1761 } 1762 1763 @Override 1764 public boolean isAutoRotateScreen() { 1765 // TODO: Move to OrientationManager. 1766 return mAutoRotateScreen; 1767 } 1768 1769 @Override 1770 public boolean onCreateOptionsMenu(Menu menu) { 1771 MenuInflater inflater = getMenuInflater(); 1772 inflater.inflate(R.menu.filmstrip_menu, menu); 1773 mActionBarMenu = menu; 1774 return super.onCreateOptionsMenu(menu); 1775 } 1776 1777 protected long getStorageSpaceBytes() { 1778 synchronized (mStorageSpaceLock) { 1779 return mStorageSpaceBytes; 1780 } 1781 } 1782 1783 protected void updateStorageSpaceAndHint() { 1784 /* 1785 * We execute disk operations on a background thread in order to 1786 * free up the UI thread. Synchronizing on the lock below ensures 1787 * that when getStorageSpaceBytes is called, the main thread waits 1788 * until this method has completed. 1789 */ 1790 (new AsyncTask<Void, Void, Void>() { 1791 @Override 1792 protected Void doInBackground(Void ... arg) { 1793 synchronized (mStorageSpaceLock) { 1794 mStorageSpaceBytes = Storage.getAvailableSpace(); 1795 } 1796 return null; 1797 } 1798 1799 @Override 1800 protected void onPostExecute(Void ignore) { 1801 updateStorageHint(getStorageSpaceBytes()); 1802 } 1803 }).execute(); 1804 } 1805 1806 protected void updateStorageHint(long storageSpace) { 1807 String message = null; 1808 if (storageSpace == Storage.UNAVAILABLE) { 1809 message = getString(R.string.no_storage); 1810 } else if (storageSpace == Storage.PREPARING) { 1811 message = getString(R.string.preparing_sd); 1812 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 1813 message = getString(R.string.access_sd_fail); 1814 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 1815 message = getString(R.string.spaceIsLow_content); 1816 } 1817 1818 if (message != null) { 1819 if (mStorageHint == null) { 1820 mStorageHint = OnScreenHint.makeText(mAppContext, message); 1821 } else { 1822 mStorageHint.setText(message); 1823 } 1824 mStorageHint.show(); 1825 } else if (mStorageHint != null) { 1826 mStorageHint.cancel(); 1827 mStorageHint = null; 1828 } 1829 } 1830 1831 protected void setResultEx(int resultCode) { 1832 mResultCodeForTesting = resultCode; 1833 setResult(resultCode); 1834 } 1835 1836 protected void setResultEx(int resultCode, Intent data) { 1837 mResultCodeForTesting = resultCode; 1838 mResultDataForTesting = data; 1839 setResult(resultCode, data); 1840 } 1841 1842 public int getResultCode() { 1843 return mResultCodeForTesting; 1844 } 1845 1846 public Intent getResultData() { 1847 return mResultDataForTesting; 1848 } 1849 1850 public boolean isSecureCamera() { 1851 return mSecureCamera; 1852 } 1853 1854 @Override 1855 public boolean isPaused() { 1856 return mPaused; 1857 } 1858 1859 @Override 1860 public int getPreferredChildModeIndex(int modeIndex) { 1861 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 1862 boolean hdrPlusOn = mSettingsManager.isHdrPlusOn(); 1863 if (hdrPlusOn && GcamHelper.hasGcamCapture()) { 1864 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1865 } 1866 } 1867 return modeIndex; 1868 } 1869 1870 @Override 1871 public void onModeSelected(int modeIndex) { 1872 if (mCurrentModeIndex == modeIndex) { 1873 return; 1874 } 1875 1876 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 1877 // Record last used camera mode for quick switching 1878 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 1879 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 1880 mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX, 1881 modeIndex); 1882 } 1883 1884 closeModule(mCurrentModule); 1885 1886 // Select the correct module index from the mode switcher index. 1887 modeIndex = getPreferredChildModeIndex(modeIndex); 1888 setModuleFromModeIndex(modeIndex); 1889 1890 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 1891 mCameraAppUI.addShutterListener(mCurrentModule); 1892 openModule(mCurrentModule); 1893 mCurrentModule.onOrientationChanged(mLastRawOrientation); 1894 // Store the module index so we can use it the next time the Camera 1895 // starts up. 1896 mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, modeIndex); 1897 } 1898 1899 /** 1900 * Shows the settings dialog. 1901 */ 1902 @Override 1903 public void onSettingsSelected() { 1904 UsageStatistics.instance().controlUsed( 1905 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 1906 Intent intent = new Intent(this, CameraSettingsActivity.class); 1907 startActivity(intent); 1908 } 1909 1910 /** 1911 * Sets the mCurrentModuleIndex, creates a new module instance for the given 1912 * index an sets it as mCurrentModule. 1913 */ 1914 private void setModuleFromModeIndex(int modeIndex) { 1915 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 1916 if (agent == null) { 1917 return; 1918 } 1919 if (!agent.requestAppForCamera()) { 1920 mCameraController.closeCamera(); 1921 } 1922 mCurrentModeIndex = agent.getModuleId(); 1923 mCurrentModule = (CameraModule) agent.createModule(this); 1924 } 1925 1926 @Override 1927 public SettingsManager getSettingsManager() { 1928 return mSettingsManager; 1929 } 1930 1931 @Override 1932 public CameraServices getServices() { 1933 return (CameraServices) getApplication(); 1934 } 1935 1936 public List<String> getSupportedModeNames() { 1937 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 1938 List<String> supported = new ArrayList<String>(); 1939 1940 for (Integer modeIndex : indices) { 1941 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 1942 if (name != null && !name.equals("")) { 1943 supported.add(name); 1944 } 1945 } 1946 return supported; 1947 } 1948 1949 @Override 1950 public ButtonManager getButtonManager() { 1951 if (mButtonManager == null) { 1952 mButtonManager = new ButtonManager(this); 1953 } 1954 return mButtonManager; 1955 } 1956 1957 /** 1958 * Creates an AlertDialog appropriate for choosing whether to enable 1959 * location on the first run of the app. 1960 */ 1961 public AlertDialog getFirstTimeLocationAlert() { 1962 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1963 builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() { 1964 @Override 1965 public void onCallback(Boolean locationOn) { 1966 mSettingsManager.setLocation(locationOn, mLocationManager); 1967 } 1968 }); 1969 if (builder != null) { 1970 return builder.create(); 1971 } else { 1972 return null; 1973 } 1974 } 1975 1976 /** 1977 * Launches an ACTION_EDIT intent for the given local data item. If 1978 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 1979 * the user start either the tiny planet editor or another photo edior. 1980 * 1981 * @param data The data item to edit. 1982 */ 1983 public void launchEditor(LocalData data) { 1984 Intent intent = new Intent(Intent.ACTION_EDIT) 1985 .setDataAndType(data.getUri(), data.getMimeType()) 1986 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 1987 try { 1988 launchActivityByIntent(intent); 1989 } catch (ActivityNotFoundException e) { 1990 launchActivityByIntent(Intent.createChooser(intent, null)); 1991 } 1992 } 1993 1994 @Override 1995 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 1996 super.onCreateContextMenu(menu, v, menuInfo); 1997 1998 MenuInflater inflater = getMenuInflater(); 1999 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2000 } 2001 2002 @Override 2003 public boolean onContextItemSelected(MenuItem item) { 2004 switch (item.getItemId()) { 2005 case R.id.tiny_planet_editor: 2006 mMyFilmstripBottomControlListener.onTinyPlanet(); 2007 return true; 2008 case R.id.photo_editor: 2009 mMyFilmstripBottomControlListener.onEdit(); 2010 return true; 2011 } 2012 return false; 2013 } 2014 2015 /** 2016 * Launch the tiny planet editor. 2017 * 2018 * @param data The data must be a 360 degree stereographically mapped 2019 * panoramic image. It will not be modified, instead a new item 2020 * with the result will be added to the filmstrip. 2021 */ 2022 public void launchTinyPlanetEditor(LocalData data) { 2023 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2024 Bundle bundle = new Bundle(); 2025 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString()); 2026 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 2027 fragment.setArguments(bundle); 2028 fragment.show(getFragmentManager(), "tiny_planet"); 2029 } 2030 2031 /** 2032 * Returns what UI mode (capture mode or filmstrip) we are in. 2033 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2034 */ 2035 private int currentUserInterfaceMode() { 2036 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2037 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2038 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2039 } 2040 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2041 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2042 } 2043 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2044 mode = NavigationChange.Mode.LENS_BLUR; 2045 } 2046 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2047 mode = NavigationChange.Mode.HDR_PLUS; 2048 } 2049 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2050 mode = NavigationChange.Mode.PHOTO_SPHERE; 2051 } 2052 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2053 mode = NavigationChange.Mode.PANORAMA; 2054 } 2055 if (mFilmstripVisible) { 2056 mode = NavigationChange.Mode.FILMSTRIP; 2057 } 2058 return mode; 2059 } 2060 2061 private void openModule(CameraModule module) { 2062 module.init(this, isSecureCamera(), isCaptureIntent()); 2063 module.hardResetSettings(mSettingsManager); 2064 module.resume(); 2065 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2066 NavigationChange.InteractionCause.BUTTON); 2067 updatePreviewVisibility(); 2068 } 2069 2070 private void closeModule(CameraModule module) { 2071 module.pause(); 2072 mCameraAppUI.clearModuleUI(); 2073 } 2074 2075 private void performDeletion() { 2076 if (!mPendingDeletion) { 2077 return; 2078 } 2079 hideUndoDeletionBar(false); 2080 mDataAdapter.executeDeletion(); 2081 } 2082 2083 public void showUndoDeletionBar() { 2084 if (mPendingDeletion) { 2085 performDeletion(); 2086 } 2087 Log.v(TAG, "showing undo bar"); 2088 mPendingDeletion = true; 2089 if (mUndoDeletionBar == null) { 2090 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2091 mAboveFilmstripControlLayout, true); 2092 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2093 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2094 button.setOnClickListener(new View.OnClickListener() { 2095 @Override 2096 public void onClick(View view) { 2097 mDataAdapter.undoDataRemoval(); 2098 hideUndoDeletionBar(true); 2099 } 2100 }); 2101 // Setting undo bar clickable to avoid touch events going through 2102 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2103 mUndoDeletionBar.setClickable(true); 2104 // When there is user interaction going on with the undo button, we 2105 // do not want to hide the undo bar. 2106 button.setOnTouchListener(new View.OnTouchListener() { 2107 @Override 2108 public boolean onTouch(View v, MotionEvent event) { 2109 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2110 mIsUndoingDeletion = true; 2111 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2112 mIsUndoingDeletion = false; 2113 } 2114 return false; 2115 } 2116 }); 2117 } 2118 mUndoDeletionBar.setAlpha(0f); 2119 mUndoDeletionBar.setVisibility(View.VISIBLE); 2120 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2121 } 2122 2123 private void hideUndoDeletionBar(boolean withAnimation) { 2124 Log.v(TAG, "Hiding undo deletion bar"); 2125 mPendingDeletion = false; 2126 if (mUndoDeletionBar != null) { 2127 if (withAnimation) { 2128 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2129 .setListener(new Animator.AnimatorListener() { 2130 @Override 2131 public void onAnimationStart(Animator animation) { 2132 // Do nothing. 2133 } 2134 2135 @Override 2136 public void onAnimationEnd(Animator animation) { 2137 mUndoDeletionBar.setVisibility(View.GONE); 2138 } 2139 2140 @Override 2141 public void onAnimationCancel(Animator animation) { 2142 // Do nothing. 2143 } 2144 2145 @Override 2146 public void onAnimationRepeat(Animator animation) { 2147 // Do nothing. 2148 } 2149 }).start(); 2150 } else { 2151 mUndoDeletionBar.setVisibility(View.GONE); 2152 } 2153 } 2154 } 2155 2156 @Override 2157 public void onOrientationChanged(int orientation) { 2158 // We keep the last known orientation. So if the user first orient 2159 // the camera then point the camera to floor or sky, we still have 2160 // the correct orientation. 2161 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) { 2162 return; 2163 } 2164 mLastRawOrientation = orientation; 2165 if (mCurrentModule != null) { 2166 mCurrentModule.onOrientationChanged(orientation); 2167 } 2168 } 2169 2170 /** 2171 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2172 * capture intent. 2173 * 2174 * @param enable {@code true} to enable swipe. 2175 */ 2176 public void setSwipingEnabled(boolean enable) { 2177 // TODO: Bring back the functionality. 2178 if (isCaptureIntent()) { 2179 // lockPreview(true); 2180 } else { 2181 // lockPreview(!enable); 2182 } 2183 } 2184 2185 // Accessor methods for getting latency times used in performance testing 2186 public long getFirstPreviewTime() { 2187 if (mCurrentModule instanceof PhotoModule) { 2188 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2189 if (coverHiddenTime != -1) { 2190 return coverHiddenTime - mOnCreateTime; 2191 } 2192 } 2193 return -1; 2194 } 2195 2196 public long getAutoFocusTime() { 2197 return (mCurrentModule instanceof PhotoModule) ? 2198 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2199 } 2200 2201 public long getShutterLag() { 2202 return (mCurrentModule instanceof PhotoModule) ? 2203 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2204 } 2205 2206 public long getShutterToPictureDisplayedTime() { 2207 return (mCurrentModule instanceof PhotoModule) ? 2208 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2209 } 2210 2211 public long getPictureDisplayedToJpegCallbackTime() { 2212 return (mCurrentModule instanceof PhotoModule) ? 2213 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2214 } 2215 2216 public long getJpegCallbackFinishTime() { 2217 return (mCurrentModule instanceof PhotoModule) ? 2218 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2219 } 2220 2221 public long getCaptureStartTime() { 2222 return (mCurrentModule instanceof PhotoModule) ? 2223 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2224 } 2225 2226 public boolean isRecording() { 2227 return (mCurrentModule instanceof VideoModule) ? 2228 ((VideoModule) mCurrentModule).isRecording() : false; 2229 } 2230 2231 public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() { 2232 return mCameraController; 2233 } 2234 2235 // For debugging purposes only. 2236 public CameraModule getCurrentModule() { 2237 return mCurrentModule; 2238 } 2239 2240 @Override 2241 public void showTutorial(AbstractTutorialOverlay tutorial) { 2242 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2243 } 2244 2245 /** 2246 * Reads the current location recording settings and passes it on to the 2247 * location manager. 2248 */ 2249 public void syncLocationManagerSetting() { 2250 mSettingsManager.syncLocationManager(mLocationManager); 2251 } 2252 2253 private void keepScreenOnForAWhile() { 2254 if (mKeepScreenOn) { 2255 return; 2256 } 2257 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2258 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2259 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2260 } 2261 2262 private void resetScreenOn() { 2263 mKeepScreenOn = false; 2264 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2265 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2266 } 2267 2268 /** 2269 * @return {@code true} if the Gallery is launched successfully. 2270 */ 2271 private boolean startGallery() { 2272 if (mGalleryIntent == null) { 2273 return false; 2274 } 2275 try { 2276 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2277 NavigationChange.InteractionCause.BUTTON); 2278 Intent startGalleryIntent = new Intent(mGalleryIntent); 2279 int currentDataId = mFilmstripController.getCurrentId(); 2280 LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId); 2281 if (currentLocalData != null) { 2282 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri()); 2283 } 2284 launchActivityByIntent(startGalleryIntent); 2285 } catch (ActivityNotFoundException e) { 2286 Log.w(TAG, "Failed to launch gallery activity, closing"); 2287 } 2288 return false; 2289 } 2290 2291 private void setNfcBeamPushUriFromData(LocalData data) { 2292 final Uri uri = data.getUri(); 2293 if (uri != Uri.EMPTY) { 2294 mNfcPushUris[0] = uri; 2295 } else { 2296 mNfcPushUris[0] = null; 2297 } 2298 } 2299 2300 /** 2301 * Updates the visibility of the filmstrip bottom controls and action bar. 2302 */ 2303 private void updateUiByData(final int dataId) { 2304 final LocalData currentData = mDataAdapter.getLocalData(dataId); 2305 if (currentData == null) { 2306 Log.w(TAG, "Current data ID not found."); 2307 hideSessionProgress(); 2308 return; 2309 } 2310 updateActionBarMenu(currentData); 2311 2312 /* Bottom controls. */ 2313 updateBottomControlsByData(currentData); 2314 2315 if (isSecureCamera()) { 2316 // We cannot show buttons in secure camera since go to other 2317 // activities might create a security hole. 2318 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2319 return; 2320 } 2321 2322 2323 setNfcBeamPushUriFromData(currentData); 2324 2325 if (!mDataAdapter.isMetadataUpdated(dataId)) { 2326 mDataAdapter.updateMetadata(dataId); 2327 } 2328 } 2329 2330 /** 2331 * Updates the bottom controls based on the data. 2332 */ 2333 private void updateBottomControlsByData(final LocalData currentData) { 2334 2335 final CameraAppUI.BottomPanel filmstripBottomPanel = 2336 mCameraAppUI.getFilmstripBottomControls(); 2337 filmstripBottomPanel.showControls(); 2338 filmstripBottomPanel.setEditButtonVisibility( 2339 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT)); 2340 filmstripBottomPanel.setShareButtonVisibility( 2341 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE)); 2342 filmstripBottomPanel.setDeleteButtonVisibility( 2343 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE)); 2344 2345 /* Progress bar */ 2346 2347 Uri contentUri = currentData.getUri(); 2348 CaptureSessionManager sessionManager = getServices() 2349 .getCaptureSessionManager(); 2350 2351 if (sessionManager.hasErrorMessage(contentUri)) { 2352 showProcessError(sessionManager.getErrorMesage(contentUri)); 2353 } else { 2354 filmstripBottomPanel.hideProgressError(); 2355 CaptureSession session = sessionManager.getSession(contentUri); 2356 2357 if (session != null) { 2358 int sessionProgress = session.getProgress(); 2359 2360 if (sessionProgress < 0) { 2361 hideSessionProgress(); 2362 } else { 2363 CharSequence progressMessage = session.getProgressMessage(); 2364 showSessionProgress(progressMessage); 2365 updateSessionProgress(sessionProgress); 2366 } 2367 } else { 2368 hideSessionProgress(); 2369 } 2370 } 2371 2372 /* View button */ 2373 2374 // We need to add this to a separate DB. 2375 final int viewButtonVisibility; 2376 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) { 2377 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2378 } else if (RgbzMetadataLoader.hasRGBZData(currentData)) { 2379 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 2380 } else { 2381 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 2382 } 2383 2384 filmstripBottomPanel.setTinyPlanetEnabled( 2385 PanoramaMetadataLoader.isPanorama360(currentData)); 2386 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 2387 } 2388 2389 private class PeekAnimationHandler extends Handler { 2390 private class DataAndCallback { 2391 LocalData mData; 2392 com.android.camera.util.Callback<Bitmap> mCallback; 2393 2394 public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap> 2395 callback) { 2396 mData = data; 2397 mCallback = callback; 2398 } 2399 } 2400 2401 public PeekAnimationHandler(Looper looper) { 2402 super(looper); 2403 } 2404 2405 /** 2406 * Starts the animation decoding job and posts a {@code Runnable} back 2407 * when when the decoding is done. 2408 * 2409 * @param data The data item to decode the thumbnail for. 2410 * @param callback {@link com.android.camera.util.Callback} after the 2411 * decoding is done. 2412 */ 2413 public void startDecodingJob(final LocalData data, 2414 final com.android.camera.util.Callback<Bitmap> callback) { 2415 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/, 2416 new DataAndCallback(data, callback)).sendToTarget(); 2417 } 2418 2419 @Override 2420 public void handleMessage(Message msg) { 2421 final LocalData data = ((DataAndCallback) msg.obj).mData; 2422 final com.android.camera.util.Callback<Bitmap> callback = 2423 ((DataAndCallback) msg.obj).mCallback; 2424 if (data == null || callback == null) { 2425 return; 2426 } 2427 2428 final Bitmap bitmap; 2429 switch (data.getLocalDataType()) { 2430 case LocalData.LOCAL_IN_PROGRESS_DATA: 2431 byte[] jpegData = Storage.getJpegForSession(data.getUri()); 2432 if (jpegData != null) { 2433 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 2434 } else { 2435 bitmap = null; 2436 } 2437 break; 2438 2439 case LocalData.LOCAL_IMAGE: 2440 FileInputStream stream; 2441 try { 2442 stream = new FileInputStream(data.getPath()); 2443 } catch (FileNotFoundException e) { 2444 Log.e(TAG, "File not found:" + data.getPath()); 2445 return; 2446 } 2447 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(), 2448 data.getRotation(), mAboveFilmstripControlLayout.getWidth(), 2449 mAboveFilmstripControlLayout.getMeasuredHeight()); 2450 if (data.getRotation() % 180 != 0) { 2451 int dummy = dim.x; 2452 dim.x = dim.y; 2453 dim.y = dummy; 2454 } 2455 bitmap = LocalDataUtil 2456 .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(), 2457 (int) (dim.x * 0.7f), (int) (dim.y * 0.7), 2458 data.getRotation(), MAX_PEEK_BITMAP_PIXELS); 2459 break; 2460 2461 case LocalData.LOCAL_VIDEO: 2462 bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath()); 2463 break; 2464 2465 default: 2466 bitmap = null; 2467 break; 2468 } 2469 2470 if (bitmap == null) { 2471 return; 2472 } 2473 2474 mMainHandler.post(new Runnable() { 2475 @Override 2476 public void run() { 2477 callback.onCallback(bitmap); 2478 } 2479 }); 2480 } 2481 } 2482 2483 private void showDetailsDialog(int dataId) { 2484 final LocalData data = mDataAdapter.getLocalData(dataId); 2485 if (data == null) { 2486 return; 2487 } 2488 MediaDetails details = data.getMediaDetails(getAndroidContext()); 2489 if (details == null) { 2490 return; 2491 } 2492 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details); 2493 detailDialog.show(); 2494 UsageStatistics.instance().mediaInteraction( 2495 fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS, 2496 NavigationChange.InteractionCause.BUTTON); 2497 } 2498 2499 /** 2500 * Show or hide action bar items depending on current data type. 2501 */ 2502 private void updateActionBarMenu(LocalData data) { 2503 if (mActionBarMenu == null) { 2504 return; 2505 } 2506 2507 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 2508 if (detailsMenuItem == null) { 2509 return; 2510 } 2511 2512 int type = data.getLocalDataType(); 2513 boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO); 2514 detailsMenuItem.setVisible(showDetails); 2515 } 2516} 2517