1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package com.android.camera; 19 20 import android.Manifest; 21 import android.animation.Animator; 22 import android.app.ActionBar; 23 import android.app.Activity; 24 import android.app.Dialog; 25 import android.app.KeyguardManager.KeyguardDismissCallback; 26 import android.content.ActivityNotFoundException; 27 import android.content.BroadcastReceiver; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.ActivityInfo; 33 import android.content.pm.PackageInfo; 34 import android.content.pm.PackageManager; 35 import android.content.res.Configuration; 36 import android.graphics.Bitmap; 37 import android.graphics.Matrix; 38 import android.graphics.RectF; 39 import android.graphics.SurfaceTexture; 40 import android.graphics.drawable.ColorDrawable; 41 import android.graphics.drawable.Drawable; 42 import android.net.Uri; 43 import android.nfc.NfcAdapter; 44 import android.nfc.NfcAdapter.CreateBeamUrisCallback; 45 import android.nfc.NfcEvent; 46 import android.os.AsyncTask; 47 import android.os.Build; 48 import android.os.Bundle; 49 import android.os.Handler; 50 import android.os.Looper; 51 import android.os.Message; 52 import android.provider.MediaStore; 53 import android.provider.Settings; 54 import android.text.TextUtils; 55 import android.util.CameraPerformanceTracker; 56 import android.view.ContextMenu; 57 import android.view.ContextMenu.ContextMenuInfo; 58 import android.view.KeyEvent; 59 import android.view.Menu; 60 import android.view.MenuInflater; 61 import android.view.MenuItem; 62 import android.view.MotionEvent; 63 import android.view.View; 64 import android.view.View.OnSystemUiVisibilityChangeListener; 65 import android.view.ViewGroup; 66 import android.view.Window; 67 import android.view.WindowManager; 68 import android.widget.FrameLayout; 69 import android.widget.ImageView; 70 import android.widget.ShareActionProvider; 71 72 import com.android.camera.app.AppController; 73 import com.android.camera.app.CameraAppUI; 74 import com.android.camera.app.CameraController; 75 import com.android.camera.app.CameraProvider; 76 import com.android.camera.app.CameraServices; 77 import com.android.camera.app.CameraServicesImpl; 78 import com.android.camera.app.FirstRunDialog; 79 import com.android.camera.app.LocationManager; 80 import com.android.camera.app.MemoryManager; 81 import com.android.camera.app.MemoryQuery; 82 import com.android.camera.app.ModuleManager; 83 import com.android.camera.app.ModuleManager.ModuleAgent; 84 import com.android.camera.app.ModuleManagerImpl; 85 import com.android.camera.app.MotionManager; 86 import com.android.camera.app.OrientationManager; 87 import com.android.camera.app.OrientationManagerImpl; 88 import com.android.camera.data.CameraFilmstripDataAdapter; 89 import com.android.camera.data.FilmstripContentObserver; 90 import com.android.camera.data.FilmstripItem; 91 import com.android.camera.data.FilmstripItemData; 92 import com.android.camera.data.FilmstripItemType; 93 import com.android.camera.data.FilmstripItemUtils; 94 import com.android.camera.data.FixedLastProxyAdapter; 95 import com.android.camera.data.GlideFilmstripManager; 96 import com.android.camera.data.LocalFilmstripDataAdapter; 97 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener; 98 import com.android.camera.data.MediaDetails; 99 import com.android.camera.data.MetadataLoader; 100 import com.android.camera.data.PhotoDataFactory; 101 import com.android.camera.data.PhotoItem; 102 import com.android.camera.data.PhotoItemFactory; 103 import com.android.camera.data.PlaceholderItem; 104 import com.android.camera.data.SessionItem; 105 import com.android.camera.data.VideoDataFactory; 106 import com.android.camera.data.VideoItemFactory; 107 import com.android.camera.debug.Log; 108 import com.android.camera.device.ActiveCameraDeviceTracker; 109 import com.android.camera.device.CameraId; 110 import com.android.camera.filmstrip.FilmstripContentPanel; 111 import com.android.camera.filmstrip.FilmstripController; 112 import com.android.camera.module.ModuleController; 113 import com.android.camera.module.ModulesInfo; 114 import com.android.camera.one.OneCameraException; 115 import com.android.camera.one.OneCameraManager; 116 import com.android.camera.one.OneCameraModule; 117 import com.android.camera.one.OneCameraOpener; 118 import com.android.camera.one.config.OneCameraFeatureConfig; 119 import com.android.camera.one.config.OneCameraFeatureConfigCreator; 120 import com.android.camera.session.CaptureSession; 121 import com.android.camera.session.CaptureSessionManager; 122 import com.android.camera.session.CaptureSessionManager.SessionListener; 123 import com.android.camera.settings.AppUpgrader; 124 import com.android.camera.settings.CameraSettingsActivity; 125 import com.android.camera.settings.Keys; 126 import com.android.camera.settings.PictureSizeLoader; 127 import com.android.camera.settings.ResolutionSetting; 128 import com.android.camera.settings.ResolutionUtil; 129 import com.android.camera.settings.SettingsManager; 130 import com.android.camera.stats.UsageStatistics; 131 import com.android.camera.stats.profiler.Profile; 132 import com.android.camera.stats.profiler.Profiler; 133 import com.android.camera.stats.profiler.Profilers; 134 import com.android.camera.tinyplanet.TinyPlanetFragment; 135 import com.android.camera.ui.AbstractTutorialOverlay; 136 import com.android.camera.ui.DetailsDialog; 137 import com.android.camera.ui.MainActivityLayout; 138 import com.android.camera.ui.ModeListView; 139 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener; 140 import com.android.camera.ui.PreviewStatusListener; 141 import com.android.camera.util.ApiHelper; 142 import com.android.camera.util.Callback; 143 import com.android.camera.util.CameraUtil; 144 import com.android.camera.util.GalleryHelper; 145 import com.android.camera.util.GcamHelper; 146 import com.android.camera.util.GoogleHelpHelper; 147 import com.android.camera.util.IntentHelper; 148 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 149 import com.android.camera.util.QuickActivity; 150 import com.android.camera.util.ReleaseHelper; 151 import com.android.camera.widget.FilmstripView; 152 import com.android.camera.widget.Preloader; 153 import com.android.camera2.R; 154 import com.android.ex.camera2.portability.CameraAgent; 155 import com.android.ex.camera2.portability.CameraAgentFactory; 156 import com.android.ex.camera2.portability.CameraExceptionHandler; 157 import com.android.ex.camera2.portability.CameraSettings; 158 import com.bumptech.glide.Glide; 159 import com.bumptech.glide.GlideBuilder; 160 import com.bumptech.glide.MemoryCategory; 161 import com.bumptech.glide.load.DecodeFormat; 162 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor; 163 import com.android.camera.exif.ExifInterface; 164 165 import com.google.common.base.Optional; 166 import com.google.common.logging.eventprotos; 167 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource; 168 import com.google.common.logging.eventprotos.MediaInteraction; 169 import com.google.common.logging.eventprotos.NavigationChange; 170 171 import java.io.File; 172 import java.lang.ref.WeakReference; 173 import java.util.ArrayList; 174 import java.util.HashMap; 175 import java.util.List; 176 177 public class CameraActivity extends QuickActivity 178 implements AppController, CameraAgent.CameraOpenCallback, 179 ShareActionProvider.OnShareTargetSelectedListener { 180 181 private static final Log.Tag TAG = new Log.Tag("CameraActivity"); 182 183 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 184 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 185 public static final String ACTION_IMAGE_CAPTURE_SECURE = 186 "android.media.action.IMAGE_CAPTURE_SECURE"; 187 188 // The intent extra for camera from secure lock screen. True if the gallery 189 // should only show newly captured pictures. sSecureAlbumId does not 190 // increment. This is used when switching between camera, camcorder, and 191 // panorama. If the extra is not set, it is in the normal camera mode. 192 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 193 194 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2; 195 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins. 196 /** Load metadata for 10 items ahead of our current. */ 197 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10; 198 199 /** Should be used wherever a context is needed. */ 200 private Context mAppContext; 201 202 /** 203 * Camera fatal error handling: 204 * 1) Present error dialog to guide users to exit the app. 205 * 2) If users hit home button, onPause should just call finish() to exit the app. 206 */ 207 private boolean mCameraFatalError = false; 208 209 /** 210 * Whether onResume should reset the view to the preview. 211 */ 212 private boolean mResetToPreviewOnResume = true; 213 214 /** 215 * This data adapter is used by FilmStripView. 216 */ 217 private VideoItemFactory mVideoItemFactory; 218 private PhotoItemFactory mPhotoItemFactory; 219 private LocalFilmstripDataAdapter mDataAdapter; 220 221 private ActiveCameraDeviceTracker mActiveCameraDeviceTracker; 222 private OneCameraOpener mOneCameraOpener; 223 private OneCameraManager mOneCameraManager; 224 private SettingsManager mSettingsManager; 225 private ResolutionSetting mResolutionSetting; 226 private ModeListView mModeListView; 227 private boolean mModeListVisible = false; 228 private int mCurrentModeIndex; 229 private CameraModule mCurrentModule; 230 private ModuleManagerImpl mModuleManager; 231 private FrameLayout mAboveFilmstripControlLayout; 232 private FilmstripController mFilmstripController; 233 private boolean mFilmstripVisible; 234 /** Whether the filmstrip fully covers the preview. */ 235 private boolean mFilmstripCoversPreview = false; 236 private int mResultCodeForTesting; 237 private Intent mResultDataForTesting; 238 private OnScreenHint mStorageHint; 239 private final Object mStorageSpaceLock = new Object(); 240 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES; 241 private boolean mAutoRotateScreen; 242 private boolean mSecureCamera; 243 private OrientationManagerImpl mOrientationManager; 244 private LocationManager mLocationManager; 245 private ButtonManager mButtonManager; 246 private Handler mMainHandler; 247 private PanoramaViewHelper mPanoramaViewHelper; 248 private ActionBar mActionBar; 249 private ViewGroup mUndoDeletionBar; 250 private boolean mIsUndoingDeletion = false; 251 private boolean mIsActivityRunning = false; 252 private FatalErrorHandler mFatalErrorHandler; 253 private boolean mHasCriticalPermissions; 254 255 private final Uri[] mNfcPushUris = new Uri[1]; 256 257 private FilmstripContentObserver mLocalImagesObserver; 258 private FilmstripContentObserver mLocalVideosObserver; 259 260 private boolean mPendingDeletion = false; 261 262 private CameraController mCameraController; 263 private boolean mPaused; 264 private CameraAppUI mCameraAppUI; 265 266 private Intent mGalleryIntent; 267 private long mOnCreateTime; 268 269 private Menu mActionBarMenu; 270 private Preloader<Integer, AsyncTask> mPreloader; 271 272 /** Can be used to play custom sounds. */ 273 private SoundPlayer mSoundPlayer; 274 275 /** Holds configuration for various OneCamera features. */ 276 private OneCameraFeatureConfig mFeatureConfig; 277 278 private static final int LIGHTS_OUT_DELAY_MS = 4000; 279 private final int BASE_SYS_UI_VISIBILITY = 280 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 281 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 282 private final Runnable mLightsOutRunnable = new Runnable() { 283 @Override 284 public void run() { 285 getWindow().getDecorView().setSystemUiVisibility( 286 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE); 287 } 288 }; 289 private MemoryManager mMemoryManager; 290 private MotionManager mMotionManager; 291 private final Profiler mProfiler = Profilers.instance().guard(); 292 293 /** First run dialog */ 294 private FirstRunDialog mFirstRunDialog; 295 296 @Override getCameraAppUI()297 public CameraAppUI getCameraAppUI() { 298 return mCameraAppUI; 299 } 300 301 @Override getModuleManager()302 public ModuleManager getModuleManager() { 303 return mModuleManager; 304 } 305 306 /** 307 * Close activity when secure app passes lock screen or screen turns 308 * off. 309 */ 310 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { 311 @Override 312 public void onReceive(Context context, Intent intent) { 313 finish(); 314 } 315 }; 316 317 /** 318 * Whether the screen is kept turned on. 319 */ 320 private boolean mKeepScreenOn; 321 private int mLastLayoutOrientation; 322 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener = 323 new CameraAppUI.BottomPanel.Listener() { 324 325 /** 326 * If the current photo is a photo sphere, this will launch the 327 * Photo Sphere panorama viewer. 328 */ 329 @Override 330 public void onExternalViewer() { 331 if (mPanoramaViewHelper == null) { 332 return; 333 } 334 final FilmstripItem data = getCurrentLocalData(); 335 if (data == null) { 336 Log.w(TAG, "Cannot open null data."); 337 return; 338 } 339 final Uri contentUri = data.getData().getUri(); 340 if (contentUri == Uri.EMPTY) { 341 Log.w(TAG, "Cannot open empty URL."); 342 return; 343 } 344 345 if (data.getMetadata().isUsePanoramaViewer()) { 346 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri); 347 } else if (data.getMetadata().isHasRgbzData()) { 348 mPanoramaViewHelper.showRgbz(contentUri); 349 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 350 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 351 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 352 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false); 353 mCameraAppUI.clearClingForViewer( 354 CameraAppUI.BottomPanel.VIEWER_REFOCUS); 355 } 356 } 357 } 358 359 @Override 360 public void onEdit() { 361 FilmstripItem data = getCurrentLocalData(); 362 if (data == null) { 363 Log.w(TAG, "Cannot edit null data."); 364 return; 365 } 366 final int currentDataId = getCurrentDataId(); 367 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 368 currentDataId), 369 MediaInteraction.InteractionType.EDIT, 370 NavigationChange.InteractionCause.BUTTON, 371 fileAgeFromAdapterAtIndex(currentDataId)); 372 launchEditor(data); 373 } 374 375 @Override 376 public void onTinyPlanet() { 377 FilmstripItem data = getCurrentLocalData(); 378 if (data == null) { 379 Log.w(TAG, "Cannot edit tiny planet on null data."); 380 return; 381 } 382 launchTinyPlanetEditor(data); 383 } 384 385 @Override 386 public void onDelete() { 387 final int currentDataId = getCurrentDataId(); 388 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 389 currentDataId), 390 MediaInteraction.InteractionType.DELETE, 391 NavigationChange.InteractionCause.BUTTON, 392 fileAgeFromAdapterAtIndex(currentDataId)); 393 removeItemAt(currentDataId); 394 } 395 396 @Override 397 public void onShare() { 398 final FilmstripItem data = getCurrentLocalData(); 399 if (data == null) { 400 Log.w(TAG, "Cannot share null data."); 401 return; 402 } 403 404 final int currentDataId = getCurrentDataId(); 405 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 406 currentDataId), 407 MediaInteraction.InteractionType.SHARE, 408 NavigationChange.InteractionCause.BUTTON, 409 fileAgeFromAdapterAtIndex(currentDataId)); 410 // If applicable, show release information before this item 411 // is shared. 412 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) { 413 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this, 414 new Callback<Void>() { 415 @Override 416 public void onCallback(Void result) { 417 share(data); 418 } 419 }); 420 } else { 421 share(data); 422 } 423 } 424 425 private void share(FilmstripItem data) { 426 Intent shareIntent = getShareIntentByData(data); 427 if (shareIntent != null) { 428 try { 429 launchActivityByIntent(shareIntent); 430 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false); 431 } catch (ActivityNotFoundException ex) { 432 // Nothing. 433 } 434 } 435 } 436 437 private int getCurrentDataId() { 438 return mFilmstripController.getCurrentAdapterIndex(); 439 } 440 441 private FilmstripItem getCurrentLocalData() { 442 return mDataAdapter.getItemAt(getCurrentDataId()); 443 } 444 445 /** 446 * Sets up the share intent and NFC properly according to the 447 * data. 448 * 449 * @param item The data to be shared. 450 */ 451 private Intent getShareIntentByData(final FilmstripItem item) { 452 Intent intent = null; 453 final Uri contentUri = item.getData().getUri(); 454 final String msgShareTo = getResources().getString(R.string.share_to); 455 456 if (item.getMetadata().isPanorama360() && 457 item.getData().getUri() != Uri.EMPTY) { 458 intent = new Intent(Intent.ACTION_SEND); 459 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE); 460 intent.putExtra(Intent.EXTRA_STREAM, contentUri); 461 } else if (item.getAttributes().canShare()) { 462 final String mimeType = item.getData().getMimeType(); 463 intent = getShareIntentFromType(mimeType); 464 if (intent != null) { 465 intent.putExtra(Intent.EXTRA_STREAM, contentUri); 466 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 467 } 468 intent = Intent.createChooser(intent, msgShareTo); 469 } 470 return intent; 471 } 472 473 /** 474 * Get the share intent according to the mimeType 475 * 476 * @param mimeType The mimeType of current data. 477 * @return the video/image's ShareIntent or null if mimeType is 478 * invalid. 479 */ 480 private Intent getShareIntentFromType(String mimeType) { 481 // Lazily create the intent object. 482 Intent intent = new Intent(Intent.ACTION_SEND); 483 if (mimeType.startsWith("video/")) { 484 intent.setType("video/*"); 485 } else { 486 if (mimeType.startsWith("image/")) { 487 intent.setType("image/*"); 488 } else { 489 Log.w(TAG, "unsupported mimeType " + mimeType); 490 } 491 } 492 return intent; 493 } 494 495 @Override 496 public void onProgressErrorClicked() { 497 FilmstripItem data = getCurrentLocalData(); 498 getServices().getCaptureSessionManager().removeErrorMessage( 499 data.getData().getUri()); 500 updateBottomControlsByData(data); 501 } 502 }; 503 504 @Override onCameraOpened(CameraAgent.CameraProxy camera)505 public void onCameraOpened(CameraAgent.CameraProxy camera) { 506 Log.v(TAG, "onCameraOpened"); 507 if (mPaused) { 508 // We've paused, but just asynchronously opened the camera. Close it 509 // because we should be releasing the camera when paused to allow 510 // other apps to access it. 511 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera"); 512 mCameraController.closeCamera(false); 513 return; 514 } 515 516 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) { 517 // We shouldn't be here. Just close the camera and leave. 518 mCameraController.closeCamera(false); 519 throw new IllegalStateException("Camera opened but the module shouldn't be " + 520 "requesting"); 521 } 522 if (mCurrentModule != null) { 523 resetExposureCompensationToDefault(camera); 524 try { 525 mCurrentModule.onCameraAvailable(camera); 526 } catch (RuntimeException ex) { 527 Log.e(TAG, "Error connecting to camera", ex); 528 mFatalErrorHandler.onCameraOpenFailure(); 529 } 530 } else { 531 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable"); 532 } 533 Log.v(TAG, "invoking onChangeCamera"); 534 mCameraAppUI.onChangeCamera(); 535 } 536 resetExposureCompensationToDefault(CameraAgent.CameraProxy camera)537 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) { 538 // Reset the exposure compensation before handing the camera to module. 539 CameraSettings cameraSettings = camera.getSettings(); 540 cameraSettings.setExposureCompensationIndex(0); 541 camera.applySettings(cameraSettings); 542 } 543 544 @Override onCameraDisabled(int cameraId)545 public void onCameraDisabled(int cameraId) { 546 Log.w(TAG, "Camera disabled: " + cameraId); 547 mFatalErrorHandler.onCameraDisabledFailure(); 548 } 549 550 @Override onDeviceOpenFailure(int cameraId, String info)551 public void onDeviceOpenFailure(int cameraId, String info) { 552 Log.w(TAG, "Camera open failure: " + info); 553 mFatalErrorHandler.onCameraOpenFailure(); 554 } 555 556 @Override onDeviceOpenedAlready(int cameraId, String info)557 public void onDeviceOpenedAlready(int cameraId, String info) { 558 Log.w(TAG, "Camera open already: " + cameraId + "," + info); 559 mFatalErrorHandler.onGenericCameraAccessFailure(); 560 } 561 562 @Override onReconnectionFailure(CameraAgent mgr, String info)563 public void onReconnectionFailure(CameraAgent mgr, String info) { 564 Log.w(TAG, "Camera reconnection failure:" + info); 565 mFatalErrorHandler.onCameraReconnectFailure(); 566 } 567 568 private static class MainHandler extends Handler { 569 final WeakReference<CameraActivity> mActivity; 570 MainHandler(CameraActivity activity, Looper looper)571 public MainHandler(CameraActivity activity, Looper looper) { 572 super(looper); 573 mActivity = new WeakReference<CameraActivity>(activity); 574 } 575 576 @Override handleMessage(Message msg)577 public void handleMessage(Message msg) { 578 CameraActivity activity = mActivity.get(); 579 if (activity == null) { 580 return; 581 } 582 switch (msg.what) { 583 584 case MSG_CLEAR_SCREEN_ON_FLAG: { 585 if (!activity.mPaused) { 586 activity.getWindow().clearFlags( 587 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 588 } 589 break; 590 } 591 } 592 } 593 } 594 fileNameFromAdapterAtIndex(int index)595 private String fileNameFromAdapterAtIndex(int index) { 596 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index); 597 if (filmstripItem == null) { 598 return ""; 599 } 600 601 File localFile = new File(filmstripItem.getData().getFilePath()); 602 return localFile.getName(); 603 } 604 fileAgeFromAdapterAtIndex(int index)605 private float fileAgeFromAdapterAtIndex(int index) { 606 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index); 607 if (filmstripItem == null) { 608 return 0; 609 } 610 611 File localFile = new File(filmstripItem.getData().getFilePath()); 612 return 0.001f * (System.currentTimeMillis() - localFile.lastModified()); 613 } 614 615 private final FilmstripContentPanel.Listener mFilmstripListener = 616 new FilmstripContentPanel.Listener() { 617 618 @Override 619 public void onSwipeOut() { 620 } 621 622 @Override 623 public void onSwipeOutBegin() { 624 mActionBar.hide(); 625 mCameraAppUI.hideBottomControls(); 626 mFilmstripCoversPreview = false; 627 updatePreviewVisibility(); 628 } 629 630 @Override 631 public void onFilmstripHidden() { 632 mFilmstripVisible = false; 633 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 634 NavigationChange.InteractionCause.SWIPE_RIGHT); 635 CameraActivity.this.setFilmstripUiVisibility(false); 636 // When the user hide the filmstrip (either swipe out or 637 // tap on back key) we move to the first item so next time 638 // when the user swipe in the filmstrip, the most recent 639 // one is shown. 640 mFilmstripController.goToFirstItem(); 641 } 642 643 @Override 644 public void onFilmstripShown() { 645 mFilmstripVisible = true; 646 mCameraAppUI.hideCaptureIndicator(); 647 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 648 NavigationChange.InteractionCause.SWIPE_LEFT); 649 updateUiByData(mFilmstripController.getCurrentAdapterIndex()); 650 } 651 652 @Override 653 public void onFocusedDataLongPressed(int adapterIndex) { 654 // Do nothing. 655 } 656 657 @Override 658 public void onFocusedDataPromoted(int adapterIndex) { 659 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 660 adapterIndex), 661 MediaInteraction.InteractionType.DELETE, 662 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex( 663 adapterIndex)); 664 removeItemAt(adapterIndex); 665 } 666 667 @Override 668 public void onFocusedDataDemoted(int adapterIndex) { 669 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 670 adapterIndex), 671 MediaInteraction.InteractionType.DELETE, 672 NavigationChange.InteractionCause.SWIPE_DOWN, 673 fileAgeFromAdapterAtIndex(adapterIndex)); 674 removeItemAt(adapterIndex); 675 } 676 677 @Override 678 public void onEnterFullScreenUiShown(int adapterIndex) { 679 if (mFilmstripVisible) { 680 CameraActivity.this.setFilmstripUiVisibility(true); 681 } 682 } 683 684 @Override 685 public void onLeaveFullScreenUiShown(int adapterIndex) { 686 // Do nothing. 687 } 688 689 @Override 690 public void onEnterFullScreenUiHidden(int adapterIndex) { 691 if (mFilmstripVisible) { 692 CameraActivity.this.setFilmstripUiVisibility(false); 693 } 694 } 695 696 @Override 697 public void onLeaveFullScreenUiHidden(int adapterIndex) { 698 // Do nothing. 699 } 700 701 @Override 702 public void onEnterFilmstrip(int adapterIndex) { 703 if (mFilmstripVisible) { 704 CameraActivity.this.setFilmstripUiVisibility(true); 705 } 706 } 707 708 @Override 709 public void onLeaveFilmstrip(int adapterIndex) { 710 // Do nothing. 711 } 712 713 @Override 714 public void onDataReloaded() { 715 if (!mFilmstripVisible) { 716 return; 717 } 718 updateUiByData(mFilmstripController.getCurrentAdapterIndex()); 719 } 720 721 @Override 722 public void onDataUpdated(int adapterIndex) { 723 if (!mFilmstripVisible) { 724 return; 725 } 726 updateUiByData(mFilmstripController.getCurrentAdapterIndex()); 727 } 728 729 @Override 730 public void onEnterZoomView(int adapterIndex) { 731 if (mFilmstripVisible) { 732 CameraActivity.this.setFilmstripUiVisibility(false); 733 } 734 } 735 736 @Override 737 public void onZoomAtIndexChanged(int adapterIndex, float zoom) { 738 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex); 739 long ageMillis = System.currentTimeMillis() 740 - filmstripItem.getData().getLastModifiedDate().getTime(); 741 742 // Do not log if items is to old or does not have a path (which is 743 // being used as a key). 744 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) || 745 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) { 746 return; 747 } 748 File localFile = new File(filmstripItem.getData().getFilePath()); 749 UsageStatistics.instance().mediaView(localFile.getName(), 750 filmstripItem.getData().getLastModifiedDate().getTime(), zoom); 751 } 752 753 @Override 754 public void onDataFocusChanged(final int prevIndex, final int newIndex) { 755 if (!mFilmstripVisible) { 756 return; 757 } 758 // TODO: This callback is UI event callback, should always 759 // happen on UI thread. Find the reason for this 760 // runOnUiThread() and fix it. 761 runOnUiThread(new Runnable() { 762 @Override 763 public void run() { 764 updateUiByData(newIndex); 765 } 766 }); 767 } 768 769 @Override 770 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) { 771 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount); 772 } 773 }; 774 775 private final FilmstripItemListener mFilmstripItemListener = 776 new FilmstripItemListener() { 777 @Override 778 public void onMetadataUpdated(List<Integer> indexes) { 779 if (mPaused) { 780 // Callback after the activity is paused. 781 return; 782 } 783 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 784 for (Integer index : indexes) { 785 if (index == currentIndex) { 786 updateUiByData(index); 787 // Currently we have only 1 data can be matched. 788 // No need to look for more, break. 789 break; 790 } 791 } 792 } 793 }; 794 gotoGallery()795 public void gotoGallery() { 796 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP, 797 NavigationChange.InteractionCause.BUTTON); 798 799 mFilmstripController.goToNextItem(); 800 } 801 802 /** 803 * If 'visible' is false, this hides the action bar. Also maintains 804 * lights-out at all times. 805 * 806 * @param visible is false, this hides the action bar and filmstrip bottom 807 * controls. 808 */ setFilmstripUiVisibility(boolean visible)809 private void setFilmstripUiVisibility(boolean visible) { 810 mLightsOutRunnable.run(); 811 mCameraAppUI.getFilmstripBottomControls().setVisible(visible); 812 if (visible != mActionBar.isShowing()) { 813 if (visible) { 814 mActionBar.show(); 815 mCameraAppUI.showBottomControls(); 816 } else { 817 mActionBar.hide(); 818 mCameraAppUI.hideBottomControls(); 819 } 820 } 821 mFilmstripCoversPreview = visible; 822 updatePreviewVisibility(); 823 } 824 hideSessionProgress()825 private void hideSessionProgress() { 826 mCameraAppUI.getFilmstripBottomControls().hideProgress(); 827 } 828 showSessionProgress(int messageId)829 private void showSessionProgress(int messageId) { 830 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls(); 831 controls.setProgressText(messageId > 0 ? getString(messageId) : ""); 832 controls.hideControls(); 833 controls.hideProgressError(); 834 controls.showProgress(); 835 } 836 showProcessError(int messageId)837 private void showProcessError(int messageId) { 838 mCameraAppUI.getFilmstripBottomControls().showProgressError( 839 messageId > 0 ? getString(messageId) : ""); 840 } 841 updateSessionProgress(int progress)842 private void updateSessionProgress(int progress) { 843 mCameraAppUI.getFilmstripBottomControls().setProgress(progress); 844 } 845 updateSessionProgressText(int messageId)846 private void updateSessionProgressText(int messageId) { 847 mCameraAppUI.getFilmstripBottomControls().setProgressText( 848 messageId > 0 ? getString(messageId) : ""); 849 } 850 851 // Candidate for deletion as Android Beam is deprecated in Android Q setupNfcBeamPush()852 private void setupNfcBeamPush() { 853 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext); 854 if (adapter == null) { 855 return; 856 } 857 858 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 859 // Disable beaming 860 adapter.setNdefPushMessage(null, CameraActivity.this); 861 return; 862 } 863 864 adapter.setBeamPushUris(null, CameraActivity.this); 865 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { 866 @Override 867 public Uri[] createBeamUris(NfcEvent event) { 868 return mNfcPushUris; 869 } 870 }, CameraActivity.this); 871 } 872 873 @Override onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent)874 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) { 875 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 876 if (currentIndex < 0) { 877 return false; 878 } 879 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex), 880 MediaInteraction.InteractionType.SHARE, 881 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex)); 882 // TODO add intent.getComponent().getPackageName() 883 return true; 884 } 885 886 // Note: All callbacks come back on the main thread. 887 private final SessionListener mSessionListener = 888 new SessionListener() { 889 @Override 890 public void onSessionQueued(final Uri uri) { 891 Log.v(TAG, "onSessionQueued: " + uri); 892 if (!Storage.instance().isSessionUri(uri)) { 893 return; 894 } 895 Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri); 896 if (newData.isPresent()) { 897 mDataAdapter.addOrUpdate(newData.get()); 898 } 899 } 900 901 @Override 902 public void onSessionUpdated(Uri uri) { 903 Log.v(TAG, "onSessionUpdated: " + uri); 904 mDataAdapter.refresh(uri); 905 } 906 907 @Override 908 public void onSessionDone(final Uri sessionUri) { 909 Log.v(TAG, "onSessionDone:" + sessionUri); 910 Uri contentUri = Storage.instance().getContentUriForSessionUri(sessionUri); 911 if (contentUri == null) { 912 mDataAdapter.refresh(sessionUri); 913 return; 914 } 915 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri); 916 917 // This can be null if e.g. a session is canceled (e.g. 918 // through discard panorama). It might be worth adding 919 // onSessionCanceled or the like this interface. 920 if (newData == null) { 921 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri); 922 return; 923 } 924 925 final int pos = mDataAdapter.findByContentUri(sessionUri); 926 if (pos == -1) { 927 // We do not have a placeholder for this image, perhaps 928 // due to the activity crashing or being killed. 929 mDataAdapter.addOrUpdate(newData); 930 } else { 931 // Make the PhotoItem aware of the session placeholder, to 932 // allow it to make a smooth transition to its content if it 933 // the session item is currently visible. 934 FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos); 935 if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE 936 && mFilmstripController.isVisible(oldSessionData)) { 937 Log.v(TAG, "session item visible, setting transition placeholder"); 938 newData.setSessionPlaceholderBitmap( 939 Storage.instance().getPlaceholderForSession(sessionUri)); 940 } 941 mDataAdapter.updateItemAt(pos, newData); 942 } 943 } 944 945 @Override 946 public void onSessionProgress(final Uri uri, final int progress) { 947 if (progress < 0) { 948 // Do nothing, there is no task for this URI. 949 return; 950 } 951 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 952 if (currentIndex == -1) { 953 return; 954 } 955 if (uri.equals( 956 mDataAdapter.getItemAt(currentIndex).getData().getUri())) { 957 updateSessionProgress(progress); 958 } 959 } 960 961 @Override 962 public void onSessionProgressText(final Uri uri, final int messageId) { 963 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 964 if (currentIndex == -1) { 965 return; 966 } 967 if (uri.equals( 968 mDataAdapter.getItemAt(currentIndex).getData().getUri())) { 969 updateSessionProgressText(messageId); 970 } 971 } 972 973 @Override 974 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) { 975 // Don't show capture indicator in Photo Sphere. 976 final int photosphereModuleId = getApplicationContext().getResources() 977 .getInteger( 978 R.integer.camera_mode_photosphere); 979 if (mCurrentModeIndex == photosphereModuleId) { 980 return; 981 } 982 indicateCapture(indicator, rotationDegrees); 983 } 984 985 @Override 986 public void onSessionFailed(Uri uri, int failureMessageId, 987 boolean removeFromFilmstrip) { 988 Log.v(TAG, "onSessionFailed:" + uri); 989 990 int failedIndex = mDataAdapter.findByContentUri(uri); 991 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 992 993 if (currentIndex == failedIndex) { 994 updateSessionProgress(0); 995 showProcessError(failureMessageId); 996 mDataAdapter.refresh(uri); 997 } 998 if (removeFromFilmstrip) { 999 mFatalErrorHandler.onMediaStorageFailure(); 1000 mDataAdapter.removeAt(failedIndex); 1001 } 1002 } 1003 1004 @Override 1005 public void onSessionCanceled(Uri uri) { 1006 Log.v(TAG, "onSessionCanceled:" + uri); 1007 int failedIndex = mDataAdapter.findByContentUri(uri); 1008 mDataAdapter.removeAt(failedIndex); 1009 } 1010 1011 @Override 1012 public void onSessionThumbnailUpdate(Bitmap bitmap) { 1013 } 1014 1015 @Override 1016 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) { 1017 } 1018 }; 1019 1020 @Override getAndroidContext()1021 public Context getAndroidContext() { 1022 return mAppContext; 1023 } 1024 1025 @Override getCameraFeatureConfig()1026 public OneCameraFeatureConfig getCameraFeatureConfig() { 1027 return mFeatureConfig; 1028 } 1029 1030 @Override createDialog()1031 public Dialog createDialog() { 1032 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen); 1033 } 1034 1035 @Override launchActivityByIntent(Intent intent)1036 public void launchActivityByIntent(Intent intent) { 1037 // Starting from L, we prefer not to start edit activity within camera's task. 1038 mResetToPreviewOnResume = false; 1039 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1040 1041 startActivity(intent); 1042 } 1043 1044 @Override getCurrentModuleIndex()1045 public int getCurrentModuleIndex() { 1046 return mCurrentModeIndex; 1047 } 1048 1049 @Override getModuleScope()1050 public String getModuleScope() { 1051 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex); 1052 return SettingsManager.getModuleSettingScope(agent.getScopeNamespace()); 1053 } 1054 1055 @Override getCameraScope()1056 public String getCameraScope() { 1057 // if an unopen camera i.e. negative ID is returned, which we've observed in 1058 // some automated scenarios, just return it as a valid separate scope 1059 // this could cause user issues, so log a stack trace noting the call path 1060 // which resulted in this scenario. 1061 1062 CameraId cameraId = mCameraController.getCurrentCameraId(); 1063 1064 if(cameraId == null) { 1065 Log.e(TAG, "Retrieving Camera Setting Scope with -1"); 1066 return SettingsManager.getCameraSettingScope("-1"); 1067 } 1068 1069 return SettingsManager.getCameraSettingScope(cameraId.getValue()); 1070 } 1071 1072 @Override getCurrentModuleController()1073 public ModuleController getCurrentModuleController() { 1074 return mCurrentModule; 1075 } 1076 1077 @Override getQuickSwitchToModuleId(int currentModuleIndex)1078 public int getQuickSwitchToModuleId(int currentModuleIndex) { 1079 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager, 1080 mAppContext); 1081 } 1082 1083 @Override getPreviewBuffer()1084 public SurfaceTexture getPreviewBuffer() { 1085 // TODO: implement this 1086 return null; 1087 } 1088 1089 @Override onPreviewReadyToStart()1090 public void onPreviewReadyToStart() { 1091 mCameraAppUI.onPreviewReadyToStart(); 1092 } 1093 1094 @Override onPreviewStarted()1095 public void onPreviewStarted() { 1096 mCameraAppUI.onPreviewStarted(); 1097 } 1098 1099 @Override addPreviewAreaSizeChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1100 public void addPreviewAreaSizeChangedListener( 1101 PreviewStatusListener.PreviewAreaChangedListener listener) { 1102 mCameraAppUI.addPreviewAreaChangedListener(listener); 1103 } 1104 1105 @Override removePreviewAreaSizeChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1106 public void removePreviewAreaSizeChangedListener( 1107 PreviewStatusListener.PreviewAreaChangedListener listener) { 1108 mCameraAppUI.removePreviewAreaChangedListener(listener); 1109 } 1110 1111 @Override setupOneShotPreviewListener()1112 public void setupOneShotPreviewListener() { 1113 mCameraController.setOneShotPreviewCallback(mMainHandler, 1114 new CameraAgent.CameraPreviewDataCallback() { 1115 @Override 1116 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) { 1117 mCurrentModule.onPreviewInitialDataReceived(); 1118 mCameraAppUI.onNewPreviewFrame(); 1119 } 1120 } 1121 ); 1122 } 1123 1124 @Override updatePreviewAspectRatio(float aspectRatio)1125 public void updatePreviewAspectRatio(float aspectRatio) { 1126 mCameraAppUI.updatePreviewAspectRatio(aspectRatio); 1127 } 1128 1129 @Override updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio)1130 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) { 1131 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio); 1132 } 1133 1134 @Override getFullscreenRect()1135 public RectF getFullscreenRect() { 1136 return mCameraAppUI.getFullscreenRect(); 1137 } 1138 1139 @Override updatePreviewTransform(Matrix matrix)1140 public void updatePreviewTransform(Matrix matrix) { 1141 mCameraAppUI.updatePreviewTransform(matrix); 1142 } 1143 1144 @Override setPreviewStatusListener(PreviewStatusListener previewStatusListener)1145 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 1146 mCameraAppUI.setPreviewStatusListener(previewStatusListener); 1147 } 1148 1149 @Override getModuleLayoutRoot()1150 public FrameLayout getModuleLayoutRoot() { 1151 return mCameraAppUI.getModuleRootView(); 1152 } 1153 1154 @Override setShutterEventsListener(ShutterEventsListener listener)1155 public void setShutterEventsListener(ShutterEventsListener listener) { 1156 // TODO: implement this 1157 } 1158 1159 @Override setShutterEnabled(boolean enabled)1160 public void setShutterEnabled(boolean enabled) { 1161 mCameraAppUI.setShutterButtonEnabled(enabled); 1162 } 1163 1164 @Override isShutterEnabled()1165 public boolean isShutterEnabled() { 1166 return mCameraAppUI.isShutterButtonEnabled(); 1167 } 1168 1169 @Override startFlashAnimation(boolean shortFlash)1170 public void startFlashAnimation(boolean shortFlash) { 1171 mCameraAppUI.startFlashAnimation(shortFlash); 1172 } 1173 1174 @Override startPreCaptureAnimation()1175 public void startPreCaptureAnimation() { 1176 // TODO: implement this 1177 } 1178 1179 @Override cancelPreCaptureAnimation()1180 public void cancelPreCaptureAnimation() { 1181 // TODO: implement this 1182 } 1183 1184 @Override startPostCaptureAnimation()1185 public void startPostCaptureAnimation() { 1186 // TODO: implement this 1187 } 1188 1189 @Override startPostCaptureAnimation(Bitmap thumbnail)1190 public void startPostCaptureAnimation(Bitmap thumbnail) { 1191 // TODO: implement this 1192 } 1193 1194 @Override cancelPostCaptureAnimation()1195 public void cancelPostCaptureAnimation() { 1196 // TODO: implement this 1197 } 1198 1199 @Override getOrientationManager()1200 public OrientationManager getOrientationManager() { 1201 return mOrientationManager; 1202 } 1203 1204 @Override getLocationManager()1205 public LocationManager getLocationManager() { 1206 return mLocationManager; 1207 } 1208 1209 @Override lockOrientation()1210 public void lockOrientation() { 1211 if (mOrientationManager != null) { 1212 mOrientationManager.lockOrientation(); 1213 } 1214 } 1215 1216 @Override unlockOrientation()1217 public void unlockOrientation() { 1218 if (mOrientationManager != null) { 1219 mOrientationManager.unlockOrientation(); 1220 } 1221 } 1222 1223 /** 1224 * If not in filmstrip, this shows the capture indicator. 1225 */ indicateCapture(final Bitmap indicator, final int rotationDegrees)1226 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) { 1227 if (mFilmstripVisible) { 1228 return; 1229 } 1230 1231 // Don't show capture indicator in Photo Sphere. 1232 // TODO: Don't reach into resources to figure out the current mode. 1233 final int photosphereModuleId = getApplicationContext().getResources().getInteger( 1234 R.integer.camera_mode_photosphere); 1235 if (mCurrentModeIndex == photosphereModuleId) { 1236 return; 1237 } 1238 1239 mMainHandler.post(new Runnable() { 1240 @Override 1241 public void run() { 1242 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule 1243 .getPeekAccessibilityString()); 1244 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees); 1245 } 1246 }); 1247 } 1248 1249 @Override notifyNewMedia(Uri uri)1250 public void notifyNewMedia(Uri uri) { 1251 // TODO: This method is running on the main thread. Also we should get 1252 // rid of that AsyncTask. 1253 1254 updateStorageSpaceAndHint(null); 1255 ContentResolver cr = getContentResolver(); 1256 String mimeType = cr.getType(uri); 1257 FilmstripItem newData = null; 1258 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) { 1259 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 1260 newData = mVideoItemFactory.queryContentUri(uri); 1261 if (newData == null) { 1262 Log.e(TAG, "Can't find video data in content resolver:" + uri); 1263 return; 1264 } 1265 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) { 1266 CameraUtil.broadcastNewPicture(mAppContext, uri); 1267 newData = mPhotoItemFactory.queryContentUri(uri); 1268 if (newData == null) { 1269 Log.e(TAG, "Can't find photo data in content resolver:" + uri); 1270 return; 1271 } 1272 } else { 1273 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri); 1274 return; 1275 } 1276 1277 // We are preloading the metadata for new video since we need the 1278 // rotation info for the thumbnail. 1279 new AsyncTask<FilmstripItem, Void, FilmstripItem>() { 1280 @Override 1281 protected FilmstripItem doInBackground(FilmstripItem... params) { 1282 FilmstripItem data = params[0]; 1283 MetadataLoader.loadMetadata(getAndroidContext(), data); 1284 return data; 1285 } 1286 1287 @Override 1288 protected void onPostExecute(final FilmstripItem data) { 1289 // TODO: Figure out why sometimes the data is aleady there. 1290 mDataAdapter.addOrUpdate(data); 1291 1292 // Legacy modules don't use CaptureSession, so we show the capture indicator when 1293 // the item was safed. 1294 if (mCurrentModule instanceof PhotoModule || 1295 mCurrentModule instanceof VideoModule) { 1296 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1297 @Override 1298 public void run() { 1299 final Optional<Bitmap> bitmap = data.generateThumbnail( 1300 mAboveFilmstripControlLayout.getWidth(), 1301 mAboveFilmstripControlLayout.getMeasuredHeight()); 1302 if (bitmap.isPresent()) { 1303 indicateCapture(bitmap.get(), 0); 1304 } 1305 } 1306 }); 1307 } 1308 } 1309 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData); 1310 } 1311 1312 @Override enableKeepScreenOn(boolean enabled)1313 public void enableKeepScreenOn(boolean enabled) { 1314 if (mPaused) { 1315 return; 1316 } 1317 1318 mKeepScreenOn = enabled; 1319 if (mKeepScreenOn) { 1320 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 1321 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1322 } else { 1323 keepScreenOnForAWhile(); 1324 } 1325 } 1326 1327 @Override getCameraProvider()1328 public CameraProvider getCameraProvider() { 1329 return mCameraController; 1330 } 1331 1332 @Override getCameraOpener()1333 public OneCameraOpener getCameraOpener() { 1334 return mOneCameraOpener; 1335 } 1336 removeItemAt(int index)1337 private void removeItemAt(int index) { 1338 mDataAdapter.removeAt(index); 1339 if (mDataAdapter.getTotalNumber() > 0) { 1340 showUndoDeletionBar(); 1341 } else { 1342 // If camera preview is the only view left in filmstrip, 1343 // no need to show undo bar. 1344 mPendingDeletion = true; 1345 performDeletion(); 1346 if (mFilmstripVisible) { 1347 mCameraAppUI.getFilmstripContentPanel().animateHide(); 1348 } 1349 } 1350 } 1351 1352 @Override onOptionsItemSelected(MenuItem item)1353 public boolean onOptionsItemSelected(MenuItem item) { 1354 // Handle presses on the action bar items 1355 switch (item.getItemId()) { 1356 case android.R.id.home: 1357 onBackPressed(); 1358 return true; 1359 case R.id.action_details: 1360 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex()); 1361 return true; 1362 case R.id.action_help_and_feedback: 1363 mResetToPreviewOnResume = false; 1364 new GoogleHelpHelper(this).launchGoogleHelp(); 1365 return true; 1366 default: 1367 return super.onOptionsItemSelected(item); 1368 } 1369 } 1370 isCaptureIntent()1371 private boolean isCaptureIntent() { 1372 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 1373 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 1374 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1375 return true; 1376 } else { 1377 return false; 1378 } 1379 } 1380 1381 /** 1382 * Note: Make sure this callback is unregistered properly when the activity 1383 * is destroyed since we're otherwise leaking the Activity reference. 1384 */ 1385 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback 1386 = new CameraExceptionHandler.CameraExceptionCallback() { 1387 @Override 1388 public void onCameraError(int errorCode) { 1389 // Not a fatal error. only do Log.e(). 1390 Log.e(TAG, "Camera error callback. error=" + errorCode); 1391 } 1392 @Override 1393 public void onCameraException( 1394 RuntimeException ex, String commandHistory, int action, int state) { 1395 Log.e(TAG, "Camera Exception", ex); 1396 UsageStatistics.instance().cameraFailure( 1397 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION, 1398 commandHistory, action, state); 1399 onFatalError(); 1400 } 1401 @Override 1402 public void onDispatchThreadException(RuntimeException ex) { 1403 Log.e(TAG, "DispatchThread Exception", ex); 1404 UsageStatistics.instance().cameraFailure( 1405 eventprotos.CameraFailure.FailureReason.API_TIMEOUT, 1406 null, UsageStatistics.NONE, UsageStatistics.NONE); 1407 onFatalError(); 1408 } 1409 private void onFatalError() { 1410 if (mCameraFatalError) { 1411 return; 1412 } 1413 mCameraFatalError = true; 1414 1415 // If the activity receives exception during onPause, just exit the app. 1416 if (mPaused && !isFinishing()) { 1417 Log.e(TAG, "Fatal error during onPause, call Activity.finish()"); 1418 finish(); 1419 } else { 1420 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA); 1421 } 1422 } 1423 }; 1424 1425 @Override onNewIntentTasks(Intent intent)1426 public void onNewIntentTasks(Intent intent) { 1427 onModeSelected(getModeIndex()); 1428 } 1429 1430 @Override onCreateTasks(Bundle state)1431 public void onCreateTasks(Bundle state) { 1432 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start(); 1433 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START); 1434 mOnCreateTime = System.currentTimeMillis(); 1435 mAppContext = getApplicationContext(); 1436 mMainHandler = new MainHandler(this, getMainLooper()); 1437 mLocationManager = new LocationManager(mAppContext, shouldUseNoOpLocation()); 1438 mOrientationManager = new OrientationManagerImpl(this, mMainHandler); 1439 mSettingsManager = getServices().getSettingsManager(); 1440 mSoundPlayer = new SoundPlayer(mAppContext); 1441 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(), 1442 getServices().getMemoryManager()); 1443 mFatalErrorHandler = new FatalErrorHandlerImpl(this); 1444 checkPermissions(); 1445 if (!mHasCriticalPermissions) { 1446 Log.v(TAG, "onCreate: Missing critical permissions."); 1447 finish(); 1448 return; 1449 } 1450 profile.mark(); 1451 if (!Glide.isSetup()) { 1452 Context context = getAndroidContext(); 1453 Glide.setup(new GlideBuilder(context) 1454 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888) 1455 .setResizeService(new FifoPriorityThreadPoolExecutor(2))); 1456 1457 Glide glide = Glide.get(context); 1458 1459 // As a camera we will use a large amount of memory 1460 // for displaying images. 1461 glide.setMemoryCategory(MemoryCategory.HIGH); 1462 } 1463 profile.mark("Glide.setup"); 1464 1465 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance(); 1466 try { 1467 mOneCameraOpener = OneCameraModule.provideOneCameraOpener( 1468 mFeatureConfig, 1469 mAppContext, 1470 mActiveCameraDeviceTracker, 1471 ResolutionUtil.getDisplayMetrics(this)); 1472 mOneCameraManager = OneCameraModule.provideOneCameraManager(); 1473 } catch (OneCameraException e) { 1474 // Log error and continue start process while showing error dialog.. 1475 Log.e(TAG, "Creating camera manager failed.", e); 1476 mFatalErrorHandler.onGenericCameraAccessFailure(); 1477 } 1478 profile.mark("OneCameraManager.get"); 1479 1480 try { 1481 mCameraController = new CameraController(mAppContext, this, mMainHandler, 1482 CameraAgentFactory.getAndroidCameraAgent(mAppContext, 1483 CameraAgentFactory.CameraApi.API_1), 1484 CameraAgentFactory.getAndroidCameraAgent(mAppContext, 1485 CameraAgentFactory.CameraApi.AUTO), 1486 mActiveCameraDeviceTracker); 1487 mCameraController.setCameraExceptionHandler( 1488 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler)); 1489 } catch (AssertionError e) { 1490 Log.e(TAG, "Creating camera controller failed.", e); 1491 mFatalErrorHandler.onGenericCameraAccessFailure(); 1492 } 1493 1494 // TODO: Try to move all the resources allocation to happen as soon as 1495 // possible so we can call module.init() at the earliest time. 1496 mModuleManager = new ModuleManagerImpl(); 1497 1498 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig); 1499 1500 AppUpgrader appUpgrader = new AppUpgrader(this); 1501 appUpgrader.upgrade(mSettingsManager); 1502 1503 // Make sure the picture sizes are correctly cached for the current OS 1504 // version. 1505 profile.mark(); 1506 try { 1507 PictureSizeLoader pictureSizeLoader = new PictureSizeLoader(mAppContext); 1508 pictureSizeLoader.computePictureSizes(); 1509 pictureSizeLoader.release(); 1510 } catch (AssertionError e) { 1511 Log.e(TAG, "Creating camera controller failed.", e); 1512 mFatalErrorHandler.onGenericCameraAccessFailure(); 1513 } 1514 profile.mark("computePictureSizes"); 1515 Keys.setDefaults(mSettingsManager, mAppContext); 1516 1517 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager, 1518 getContentResolver()); 1519 1520 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 1521 // We suppress this flag via theme when drawing the system preview 1522 // background, but once we create activity here, reactivate to the 1523 // default value. The default is important for L, we don't want to 1524 // change app behavior, just starting background drawable layout. 1525 if (ApiHelper.isLOrHigher()) { 1526 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 1527 } 1528 1529 profile.mark(); 1530 setContentView(R.layout.activity_main); 1531 profile.mark("setContentView()"); 1532 // A window background is set in styles.xml for the system to show a 1533 // drawable background with gray color and camera icon before the 1534 // activity is created. We set the background to null here to prevent 1535 // overdraw, all views must take care of drawing backgrounds if 1536 // necessary. This call to setBackgroundDrawable must occur after 1537 // setContentView, otherwise a background may be set again from the 1538 // style. 1539 getWindow().setBackgroundDrawable(null); 1540 1541 mActionBar = getActionBar(); 1542 // set actionbar background to 100% or 50% transparent 1543 if (ApiHelper.isLOrHigher()) { 1544 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000)); 1545 } else { 1546 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000)); 1547 } 1548 1549 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); 1550 mModeListView.init(mModuleManager.getSupportedModeIndexList()); 1551 if (ApiHelper.HAS_ROTATION_ANIMATION) { 1552 setRotationAnimation(); 1553 } 1554 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() { 1555 @Override 1556 public void onVisibilityChanged(boolean visible) { 1557 mModeListVisible = visible; 1558 mCameraAppUI.setShutterButtonImportantToA11y(!visible); 1559 updatePreviewVisibility(); 1560 } 1561 }); 1562 1563 // Check if this is in the secure camera mode. 1564 Intent intent = getIntent(); 1565 String action = intent.getAction(); 1566 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 1567 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 1568 mSecureCamera = true; 1569 } else { 1570 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 1571 } 1572 1573 if (mSecureCamera) { 1574 // Change the window flags so that secure camera can show when 1575 // locked 1576 Window win = getWindow(); 1577 WindowManager.LayoutParams params = win.getAttributes(); 1578 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 1579 win.setAttributes(params); 1580 1581 // Filter for screen off so that we can finish secure camera 1582 // activity when screen is off. 1583 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF); 1584 registerReceiver(mShutdownReceiver, filter_screen_off); 1585 1586 // Filter for phone unlock so that we can finish secure camera 1587 // via this UI path: 1588 // 1. from secure lock screen, user starts secure camera 1589 // 2. user presses home button 1590 // 3. user unlocks phone 1591 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT); 1592 registerReceiver(mShutdownReceiver, filter_user_unlock); 1593 } 1594 mCameraAppUI = new CameraAppUI(this, 1595 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent()); 1596 1597 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener); 1598 1599 mAboveFilmstripControlLayout = 1600 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout); 1601 1602 // Add the session listener so we can track the session progress 1603 // updates. 1604 getServices().getCaptureSessionManager().addSessionListener(mSessionListener); 1605 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController(); 1606 mFilmstripController.setImageGap( 1607 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 1608 profile.mark("Configure Camera UI"); 1609 1610 mPanoramaViewHelper = new PanoramaViewHelper(this); 1611 mPanoramaViewHelper.onCreate(); 1612 1613 ContentResolver appContentResolver = mAppContext.getContentResolver(); 1614 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext); 1615 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver, 1616 new PhotoDataFactory()); 1617 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver, 1618 new VideoDataFactory()); 1619 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener); 1620 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1621 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 1622 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS); 1623 } 1624 1625 setModuleFromModeIndex(getModeIndex()); 1626 1627 profile.mark(); 1628 mCameraAppUI.prepareModuleUI(); 1629 profile.mark("Init Current Module UI"); 1630 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent()); 1631 profile.mark("Init CurrentModule"); 1632 1633 preloadFilmstripItems(); 1634 1635 // Candidate for deletion as Android Beam is deprecated in Android Q 1636 setupNfcBeamPush(); 1637 1638 mLocalImagesObserver = new FilmstripContentObserver(); 1639 mLocalVideosObserver = new FilmstripContentObserver(); 1640 1641 getContentResolver().registerContentObserver( 1642 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1643 mLocalImagesObserver); 1644 getContentResolver().registerContentObserver( 1645 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1646 mLocalVideosObserver); 1647 1648 mMemoryManager = getServices().getMemoryManager(); 1649 1650 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1651 @Override 1652 public void run() { 1653 HashMap memoryData = mMemoryManager.queryMemory(); 1654 UsageStatistics.instance().reportMemoryConsumed(memoryData, 1655 MemoryQuery.REPORT_LABEL_LAUNCH); 1656 } 1657 }); 1658 1659 mMotionManager = getServices().getMotionManager(); 1660 1661 mFirstRunDialog = new FirstRunDialog(this, 1662 this /* as context */, 1663 mResolutionSetting, 1664 mSettingsManager, 1665 mOneCameraManager, 1666 new FirstRunDialog.FirstRunDialogListener() { 1667 @Override 1668 public void onFirstRunStateReady() { 1669 // Run normal resume tasks. 1670 resume(); 1671 } 1672 1673 @Override 1674 public void onFirstRunDialogCancelled() { 1675 // App isn't functional until users finish first run dialog. 1676 // We need to finish here since users hit back button during 1677 // first run dialog (b/19593942). 1678 finish(); 1679 } 1680 1681 @Override 1682 public void onCameraAccessException() { 1683 mFatalErrorHandler.onGenericCameraAccessFailure(); 1684 } 1685 }); 1686 profile.stop(); 1687 } 1688 1689 /** 1690 * Get the current mode index from the Intent or from persistent 1691 * settings. 1692 */ getModeIndex()1693 private int getModeIndex() { 1694 int modeIndex = -1; 1695 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo); 1696 int videoIndex = getResources().getInteger(R.integer.camera_mode_video); 1697 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1698 int captureIntentIndex = 1699 getResources().getInteger(R.integer.camera_mode_capture_intent); 1700 String intentAction = getIntent().getAction(); 1701 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction) 1702 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) { 1703 modeIndex = videoIndex; 1704 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction) 1705 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) { 1706 // Capture intent. 1707 modeIndex = captureIntentIndex; 1708 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction) 1709 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction) 1710 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) { 1711 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1712 Keys.KEY_CAMERA_MODULE_LAST_USED); 1713 1714 // For upgraders who have not seen the aspect ratio selection screen, 1715 // we need to drop them back in the photo module and have them select 1716 // aspect ratio. 1717 // TODO: Move this to SettingsManager as an upgrade procedure. 1718 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1719 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { 1720 modeIndex = photoIndex; 1721 } 1722 } else { 1723 // If the activity has not been started using an explicit intent, 1724 // read the module index from the last time the user changed modes 1725 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1726 Keys.KEY_STARTUP_MODULE_INDEX); 1727 if ((modeIndex == gcamIndex && 1728 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) { 1729 modeIndex = photoIndex; 1730 } 1731 } 1732 return modeIndex; 1733 } 1734 1735 /** 1736 * Incase the calling package doesn't have ACCESS_FINE_LOCATION permissions, we should not pass 1737 * it valid location information in exif. 1738 */ shouldUseNoOpLocation()1739 private boolean shouldUseNoOpLocation () { 1740 String callingPackage = getCallingPackage(); 1741 if (callingPackage == null) { 1742 // Activity not started through startActivityForResult. 1743 return false; 1744 } 1745 PackageInfo packageInfo = null; 1746 try { 1747 packageInfo = getPackageManager().getPackageInfo(callingPackage, 1748 PackageManager.GET_PERMISSIONS); 1749 } catch (Exception e) { 1750 Log.w(TAG, "Unable to get PackageInfo for callingPackage " + callingPackage); 1751 } 1752 if (packageInfo != null) { 1753 if (packageInfo.requestedPermissions == null) { 1754 // No-permissions at all, were requested by the calling app. 1755 return true; 1756 } 1757 for (int i = 0; i < packageInfo.requestedPermissions.length; i++) { 1758 if (packageInfo.requestedPermissions[i].equals( 1759 Manifest.permission.ACCESS_FINE_LOCATION) && 1760 (packageInfo.requestedPermissionsFlags[i] & 1761 PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { 1762 return false; 1763 } 1764 } 1765 } 1766 return true; 1767 } 1768 /** 1769 * Call this whenever the mode drawer or filmstrip change the visibility 1770 * state. 1771 */ updatePreviewVisibility()1772 private void updatePreviewVisibility() { 1773 if (mCurrentModule == null) { 1774 return; 1775 } 1776 1777 int visibility = getPreviewVisibility(); 1778 mCameraAppUI.onPreviewVisiblityChanged(visibility); 1779 updatePreviewRendering(visibility); 1780 mCurrentModule.onPreviewVisibilityChanged(visibility); 1781 } 1782 updatePreviewRendering(int visibility)1783 private void updatePreviewRendering(int visibility) { 1784 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1785 mCameraAppUI.pausePreviewRendering(); 1786 } else { 1787 mCameraAppUI.resumePreviewRendering(); 1788 } 1789 } 1790 getPreviewVisibility()1791 private int getPreviewVisibility() { 1792 if (mFilmstripCoversPreview) { 1793 return ModuleController.VISIBILITY_HIDDEN; 1794 } else if (mModeListVisible){ 1795 return ModuleController.VISIBILITY_COVERED; 1796 } else { 1797 return ModuleController.VISIBILITY_VISIBLE; 1798 } 1799 } 1800 setRotationAnimation()1801 private void setRotationAnimation() { 1802 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1803 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1804 Window win = getWindow(); 1805 WindowManager.LayoutParams winParams = win.getAttributes(); 1806 winParams.rotationAnimation = rotationAnimation; 1807 win.setAttributes(winParams); 1808 } 1809 1810 @Override onUserInteraction()1811 public void onUserInteraction() { 1812 super.onUserInteraction(); 1813 if (!isFinishing()) { 1814 keepScreenOnForAWhile(); 1815 } 1816 } 1817 1818 @Override dispatchTouchEvent(MotionEvent ev)1819 public boolean dispatchTouchEvent(MotionEvent ev) { 1820 boolean result = super.dispatchTouchEvent(ev); 1821 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1822 // Real deletion is postponed until the next user interaction after 1823 // the gesture that triggers deletion. Until real deletion is 1824 // performed, users can click the undo button to bring back the 1825 // image that they chose to delete. 1826 if (mPendingDeletion && !mIsUndoingDeletion) { 1827 performDeletion(); 1828 } 1829 } 1830 return result; 1831 } 1832 1833 @Override onPauseTasks()1834 public void onPauseTasks() { 1835 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE); 1836 Profile profile = mProfiler.create("CameraActivity.onPause").start(); 1837 1838 /* 1839 * Save the last module index after all secure camera and icon launches, 1840 * not just on mode switches. 1841 * 1842 * Right now we exclude capture intents from this logic, because we also 1843 * ignore the cross-Activity recovery logic in onStart for capture intents. 1844 */ 1845 if (!isCaptureIntent()) { 1846 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 1847 Keys.KEY_STARTUP_MODULE_INDEX, 1848 mCurrentModeIndex); 1849 } 1850 1851 mPaused = true; 1852 mCameraAppUI.hideCaptureIndicator(); 1853 mFirstRunDialog.dismiss(); 1854 1855 // Delete photos that are pending deletion 1856 performDeletion(); 1857 mCurrentModule.pause(); 1858 mOrientationManager.pause(); 1859 mPanoramaViewHelper.onPause(); 1860 1861 mLocalImagesObserver.setForegroundChangeListener(null); 1862 mLocalImagesObserver.setActivityPaused(true); 1863 mLocalVideosObserver.setActivityPaused(true); 1864 if (mPreloader != null) { 1865 mPreloader.cancelAllLoads(); 1866 } 1867 resetScreenOn(); 1868 1869 mMotionManager.stop(); 1870 1871 // Always stop recording location when paused. Resume will start 1872 // location recording again if the location setting is on. 1873 mLocationManager.recordLocation(false); 1874 1875 UsageStatistics.instance().backgrounded(); 1876 1877 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home 1878 // button. Let's just kill the process. 1879 if (mCameraFatalError && !isFinishing()) { 1880 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()"); 1881 finish(); 1882 } else { 1883 // Close the camera and wait for the operation done. 1884 Log.v(TAG, "onPause closing camera"); 1885 if (mCameraController != null) { 1886 mCameraController.closeCamera(true); 1887 } 1888 } 1889 1890 profile.stop(); 1891 } 1892 1893 @Override onResumeTasks()1894 public void onResumeTasks() { 1895 mPaused = false; 1896 checkPermissions(); 1897 if (!mHasCriticalPermissions) { 1898 Log.v(TAG, "onResume: Missing critical permissions."); 1899 finish(); 1900 return; 1901 } 1902 if (!isSecureCamera() && !isCaptureIntent()) { 1903 // Show the dialog if necessary. The rest resume logic will be invoked 1904 // at the onFirstRunStateReady() callback. 1905 try { 1906 mFirstRunDialog.showIfNecessary(); 1907 } catch (AssertionError e) { 1908 Log.e(TAG, "Creating camera controller failed.", e); 1909 mFatalErrorHandler.onGenericCameraAccessFailure(); 1910 } 1911 } else { 1912 // In secure mode from lockscreen, we go straight to camera and will 1913 // show first run dialog next time user enters launcher. 1914 Log.v(TAG, "in secure mode, skipping first run dialog check"); 1915 resume(); 1916 } 1917 } 1918 1919 /** 1920 * Checks if any of the needed Android runtime permissions are missing. 1921 * If they are, then launch the permissions activity under one of the following conditions: 1922 * a) The permissions dialogs have not run yet. We will ask for permission only once. 1923 * b) If the missing permissions are critical to the app running, we will display a fatal error dialog. 1924 * Critical permissions are: camera, microphone and storage. The app cannot run without them. 1925 * Non-critical permission is location. 1926 */ checkPermissions()1927 private void checkPermissions() { 1928 if (!ApiHelper.isMOrHigher()) { 1929 Log.v(TAG, "not running on M, skipping permission checks"); 1930 mHasCriticalPermissions = true; 1931 return; 1932 } 1933 1934 if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && 1935 checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { 1936 mHasCriticalPermissions = true; 1937 } else { 1938 mHasCriticalPermissions = false; 1939 } 1940 if (!mHasCriticalPermissions || (mSettingsManager.getBoolean( 1941 SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION) && 1942 (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) 1943 != PackageManager.PERMISSION_GRANTED) && 1944 !mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1945 Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS))) { 1946 // TODO: Convert PermissionsActivity into a dialog so we 1947 // don't lose the state of CameraActivity. 1948 Intent intent = new Intent(this, PermissionsActivity.class); 1949 startActivity(intent); 1950 finish(); 1951 } 1952 } 1953 preloadFilmstripItems()1954 private void preloadFilmstripItems() { 1955 if (mDataAdapter == null) { 1956 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext, 1957 mPhotoItemFactory, mVideoItemFactory); 1958 mDataAdapter.setLocalDataListener(mFilmstripItemListener); 1959 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter, 1960 mDataAdapter); 1961 if (!mSecureCamera) { 1962 mFilmstripController.setDataAdapter(mDataAdapter); 1963 if (!isCaptureIntent()) { 1964 mDataAdapter.requestLoad(new Callback<Void>() { 1965 @Override 1966 public void onCallback(Void result) { 1967 fillTemporarySessions(); 1968 } 1969 }); 1970 } 1971 } else { 1972 // Put a lock placeholder as the last image by setting its date to 1973 // 0. 1974 ImageView v = (ImageView) getLayoutInflater().inflate( 1975 R.layout.secure_album_placeholder, null); 1976 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal()); 1977 v.setOnClickListener(new View.OnClickListener() { 1978 @Override 1979 public void onClick(View view) { 1980 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 1981 NavigationChange.InteractionCause.BUTTON); 1982 startGallery(); 1983 finish(); 1984 } 1985 }); 1986 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera)); 1987 mDataAdapter = new FixedLastProxyAdapter( 1988 mAppContext, 1989 mDataAdapter, 1990 new PlaceholderItem( 1991 v, 1992 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER, 1993 v.getDrawable().getIntrinsicWidth(), 1994 v.getDrawable().getIntrinsicHeight())); 1995 // Flush out all the original data. 1996 mDataAdapter.clear(); 1997 mFilmstripController.setDataAdapter(mDataAdapter); 1998 } 1999 } 2000 } 2001 resume()2002 private void resume() { 2003 Profile profile = mProfiler.create("CameraActivity.resume").start(); 2004 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME); 2005 Log.v(TAG, "Build info: " + Build.DISPLAY); 2006 updateStorageSpaceAndHint(null); 2007 2008 mLastLayoutOrientation = getResources().getConfiguration().orientation; 2009 2010 // TODO: Handle this in OrientationManager. 2011 // Auto-rotate off 2012 if (Settings.System.getInt(getContentResolver(), 2013 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 2014 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 2015 mAutoRotateScreen = false; 2016 } else { 2017 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 2018 mAutoRotateScreen = true; 2019 } 2020 2021 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and 2022 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to 2023 // lockscreen onResume->onPause->onResume sequence. 2024 int source; 2025 String action = getIntent().getAction(); 2026 if (action == null) { 2027 source = ForegroundSource.UNKNOWN_SOURCE; 2028 } else { 2029 switch (action) { 2030 case MediaStore.ACTION_IMAGE_CAPTURE: 2031 source = ForegroundSource.ACTION_IMAGE_CAPTURE; 2032 break; 2033 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA: 2034 // was UNKNOWN_SOURCE in Fishlake. 2035 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA; 2036 break; 2037 case MediaStore.INTENT_ACTION_VIDEO_CAMERA: 2038 // was UNKNOWN_SOURCE in Fishlake. 2039 source = ForegroundSource.ACTION_VIDEO_CAMERA; 2040 break; 2041 case MediaStore.ACTION_VIDEO_CAPTURE: 2042 source = ForegroundSource.ACTION_VIDEO_CAPTURE; 2043 break; 2044 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE: 2045 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake. 2046 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE; 2047 break; 2048 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE: 2049 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE; 2050 break; 2051 case Intent.ACTION_MAIN: 2052 source = ForegroundSource.ACTION_MAIN; 2053 break; 2054 default: 2055 source = ForegroundSource.UNKNOWN_SOURCE; 2056 break; 2057 } 2058 } 2059 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(), 2060 isKeyguardSecure(), isKeyguardLocked(), 2061 mStartupOnCreate, mExecutionStartNanoTime); 2062 2063 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext); 2064 if (ApiHelper.isLOrHigher()) { 2065 // hide the up affordance for L devices, it's not very Materially 2066 mActionBar.setDisplayShowHomeEnabled(false); 2067 } 2068 2069 mOrientationManager.resume(); 2070 2071 mCurrentModule.hardResetSettings(mSettingsManager); 2072 2073 profile.mark(); 2074 mCurrentModule.resume(); 2075 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2076 NavigationChange.InteractionCause.BUTTON); 2077 setSwipingEnabled(true); 2078 profile.mark("mCurrentModule.resume"); 2079 2080 if (!mResetToPreviewOnResume) { 2081 FilmstripItem item = mDataAdapter.getItemAt( 2082 mFilmstripController.getCurrentAdapterIndex()); 2083 if (item != null) { 2084 mDataAdapter.refresh(item.getData().getUri()); 2085 } 2086 } 2087 2088 // The share button might be disabled to avoid double tapping. 2089 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true); 2090 // Default is showing the preview, unless disabled by explicitly 2091 // starting an activity we want to return from to the filmstrip rather 2092 // than the preview. 2093 mResetToPreviewOnResume = true; 2094 2095 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 2096 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 2097 if (!mSecureCamera) { 2098 // If it's secure camera, requestLoad() should not be called 2099 // as it will load all the data. 2100 if (!mFilmstripVisible) { 2101 mDataAdapter.requestLoad(new Callback<Void>() { 2102 @Override 2103 public void onCallback(Void result) { 2104 fillTemporarySessions(); 2105 } 2106 }); 2107 } else { 2108 mDataAdapter.requestLoadNewPhotos(); 2109 } 2110 } 2111 } 2112 mLocalImagesObserver.setActivityPaused(false); 2113 mLocalVideosObserver.setActivityPaused(false); 2114 if (!mSecureCamera) { 2115 mLocalImagesObserver.setForegroundChangeListener( 2116 new FilmstripContentObserver.ChangeListener() { 2117 @Override 2118 public void onChange() { 2119 mDataAdapter.requestLoadNewPhotos(); 2120 } 2121 }); 2122 } 2123 2124 keepScreenOnForAWhile(); 2125 2126 // Lights-out mode at all times. 2127 final View rootView = findViewById(R.id.activity_root_view); 2128 mLightsOutRunnable.run(); 2129 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( 2130 new OnSystemUiVisibilityChangeListener() { 2131 @Override 2132 public void onSystemUiVisibilityChange(int visibility) { 2133 mMainHandler.removeCallbacks(mLightsOutRunnable); 2134 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS); 2135 } 2136 }); 2137 2138 profile.mark(); 2139 mPanoramaViewHelper.onResume(); 2140 profile.mark("mPanoramaViewHelper.onResume()"); 2141 2142 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager); 2143 // Enable location recording if the setting is on. 2144 final boolean locationRecordingEnabled = 2145 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION); 2146 mLocationManager.recordLocation(locationRecordingEnabled); 2147 2148 final int previewVisibility = getPreviewVisibility(); 2149 updatePreviewRendering(previewVisibility); 2150 2151 mMotionManager.start(); 2152 profile.stop(); 2153 } 2154 fillTemporarySessions()2155 private void fillTemporarySessions() { 2156 if (mSecureCamera) { 2157 return; 2158 } 2159 // There might be sessions still in flight (processed by our service). 2160 // Make sure they're added to the filmstrip. 2161 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener); 2162 } 2163 2164 @Override onStartTasks()2165 public void onStartTasks() { 2166 mIsActivityRunning = true; 2167 mPanoramaViewHelper.onStart(); 2168 2169 /* 2170 * If we're starting after launching a different Activity (lockscreen), 2171 * we need to use the last mode used in the other Activity, and 2172 * not the old one from this Activity. 2173 * 2174 * This needs to happen before CameraAppUI.resume() in order to set the 2175 * mode cover icon to the actual last mode used. 2176 * 2177 * Right now we exclude capture intents from this logic. 2178 */ 2179 int modeIndex = getModeIndex(); 2180 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) { 2181 onModeSelected(modeIndex); 2182 } 2183 2184 if (mResetToPreviewOnResume) { 2185 mCameraAppUI.resume(); 2186 mResetToPreviewOnResume = false; 2187 } 2188 } 2189 2190 @Override onStopTasks()2191 protected void onStopTasks() { 2192 mIsActivityRunning = false; 2193 mPanoramaViewHelper.onStop(); 2194 2195 mLocationManager.disconnect(); 2196 } 2197 2198 @Override onDestroyTasks()2199 public void onDestroyTasks() { 2200 if (mSecureCamera) { 2201 unregisterReceiver(mShutdownReceiver); 2202 } 2203 2204 // Ensure anything that checks for "isPaused" returns true. 2205 mPaused = true; 2206 2207 mSettingsManager.removeAllListeners(); 2208 if (mCameraController != null) { 2209 mCameraController.removeCallbackReceiver(); 2210 mCameraController.setCameraExceptionHandler(null); 2211 } 2212 if (mLocalImagesObserver != null) { 2213 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 2214 } 2215 if (mLocalVideosObserver != null) { 2216 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 2217 } 2218 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener); 2219 if (mCameraAppUI != null) { 2220 mCameraAppUI.onDestroy(); 2221 } 2222 if (mModeListView != null) { 2223 mModeListView.setVisibilityChangedListener(null); 2224 } 2225 mCameraController = null; 2226 mSettingsManager = null; 2227 mOrientationManager = null; 2228 mButtonManager = null; 2229 if (mSoundPlayer != null) { 2230 mSoundPlayer.release(); 2231 } 2232 if (mFirstRunDialog != null) { 2233 mFirstRunDialog.dismiss(); 2234 } 2235 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1); 2236 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO); 2237 } 2238 2239 @Override onConfigurationChanged(Configuration config)2240 public void onConfigurationChanged(Configuration config) { 2241 super.onConfigurationChanged(config); 2242 Log.v(TAG, "onConfigurationChanged"); 2243 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 2244 return; 2245 } 2246 2247 if (mLastLayoutOrientation != config.orientation) { 2248 mLastLayoutOrientation = config.orientation; 2249 mCurrentModule.onLayoutOrientationChanged( 2250 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 2251 } 2252 } 2253 2254 @Override onKeyDown(int keyCode, KeyEvent event)2255 public boolean onKeyDown(int keyCode, KeyEvent event) { 2256 if (!mFilmstripVisible) { 2257 if (mCurrentModule.onKeyDown(keyCode, event)) { 2258 return true; 2259 } 2260 // Prevent software keyboard or voice search from showing up. 2261 if (keyCode == KeyEvent.KEYCODE_SEARCH 2262 || keyCode == KeyEvent.KEYCODE_MENU) { 2263 if (event.isLongPress()) { 2264 return true; 2265 } 2266 } 2267 } 2268 2269 return super.onKeyDown(keyCode, event); 2270 } 2271 2272 @Override onKeyUp(int keyCode, KeyEvent event)2273 public boolean onKeyUp(int keyCode, KeyEvent event) { 2274 if (!mFilmstripVisible) { 2275 // If a module is in the middle of capture, it should 2276 // consume the key event. 2277 if (mCurrentModule.onKeyUp(keyCode, event)) { 2278 return true; 2279 } else if (keyCode == KeyEvent.KEYCODE_MENU 2280 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 2281 // Let the mode list view consume the event. 2282 mCameraAppUI.openModeList(); 2283 return true; 2284 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 2285 mCameraAppUI.showFilmstrip(); 2286 return true; 2287 } 2288 } else { 2289 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 2290 mFilmstripController.goToNextItem(); 2291 return true; 2292 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 2293 boolean wentToPrevious = mFilmstripController.goToPreviousItem(); 2294 if (!wentToPrevious) { 2295 // at beginning of filmstrip, hide and go back to preview 2296 mCameraAppUI.hideFilmstrip(); 2297 } 2298 return true; 2299 } 2300 } 2301 return super.onKeyUp(keyCode, event); 2302 } 2303 2304 @Override onBackPressed()2305 public void onBackPressed() { 2306 if (!mCameraAppUI.onBackPressed()) { 2307 if (!mCurrentModule.onBackPressed()) { 2308 super.onBackPressed(); 2309 } 2310 } 2311 } 2312 2313 @Override isAutoRotateScreen()2314 public boolean isAutoRotateScreen() { 2315 // TODO: Move to OrientationManager. 2316 return mAutoRotateScreen; 2317 } 2318 2319 @Override onCreateOptionsMenu(Menu menu)2320 public boolean onCreateOptionsMenu(Menu menu) { 2321 MenuInflater inflater = getMenuInflater(); 2322 inflater.inflate(R.menu.filmstrip_menu, menu); 2323 mActionBarMenu = menu; 2324 2325 // add a button for launching the gallery 2326 if (mGalleryIntent != null) { 2327 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent); 2328 if (appName != null) { 2329 MenuItem menuItem = menu.add(appName); 2330 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 2331 menuItem.setIntent(mGalleryIntent); 2332 2333 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent); 2334 if (galleryLogo != null) { 2335 menuItem.setIcon(galleryLogo); 2336 } 2337 } 2338 } 2339 2340 return super.onCreateOptionsMenu(menu); 2341 } 2342 2343 @Override onPrepareOptionsMenu(Menu menu)2344 public boolean onPrepareOptionsMenu(Menu menu) { 2345 if (isSecureCamera() && !ApiHelper.isLOrHigher()) { 2346 // Compatibility pre-L: launching new activities right above 2347 // lockscreen does not reliably work, only show help if not secure 2348 menu.removeItem(R.id.action_help_and_feedback); 2349 } 2350 2351 return super.onPrepareOptionsMenu(menu); 2352 } 2353 getStorageSpaceBytes()2354 protected long getStorageSpaceBytes() { 2355 synchronized (mStorageSpaceLock) { 2356 return mStorageSpaceBytes; 2357 } 2358 } 2359 2360 protected interface OnStorageUpdateDoneListener { onStorageUpdateDone(long bytes)2361 public void onStorageUpdateDone(long bytes); 2362 } 2363 updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback)2364 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) { 2365 /* 2366 * We execute disk operations on a background thread in order to 2367 * free up the UI thread. Synchronizing on the lock below ensures 2368 * that when getStorageSpaceBytes is called, the main thread waits 2369 * until this method has completed. 2370 * 2371 * However, .execute() does not ensure this execution block will be 2372 * run right away (.execute() schedules this AsyncTask for sometime 2373 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 2374 * tries to execute the task in parellel with other AsyncTasks, but 2375 * there's still no guarantee). 2376 * e.g. don't call this then immediately call getStorageSpaceBytes(). 2377 * Instead, pass in an OnStorageUpdateDoneListener. 2378 */ 2379 (new AsyncTask<Void, Void, Long>() { 2380 @Override 2381 protected Long doInBackground(Void ... arg) { 2382 synchronized (mStorageSpaceLock) { 2383 mStorageSpaceBytes = Storage.instance().getAvailableSpace(); 2384 return mStorageSpaceBytes; 2385 } 2386 } 2387 2388 @Override 2389 protected void onPostExecute(Long bytes) { 2390 updateStorageHint(bytes); 2391 // This callback returns after I/O to check disk, so we could be 2392 // pausing and shutting down. If so, don't bother invoking. 2393 if (callback != null && !mPaused) { 2394 callback.onStorageUpdateDone(bytes); 2395 } else { 2396 Log.v(TAG, "ignoring storage callback after activity pause"); 2397 } 2398 } 2399 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2400 } 2401 updateStorageHint(long storageSpace)2402 protected void updateStorageHint(long storageSpace) { 2403 if (!mIsActivityRunning) { 2404 return; 2405 } 2406 2407 String message = null; 2408 if (storageSpace == Storage.UNAVAILABLE) { 2409 message = getString(R.string.no_storage); 2410 } else if (storageSpace == Storage.PREPARING) { 2411 message = getString(R.string.preparing_sd); 2412 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 2413 message = getString(R.string.access_sd_fail); 2414 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 2415 message = getString(R.string.spaceIsLow_content); 2416 } 2417 2418 if (message != null) { 2419 Log.w(TAG, "Storage warning: " + message); 2420 if (mStorageHint == null) { 2421 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message); 2422 } else { 2423 mStorageHint.setText(message); 2424 } 2425 mStorageHint.show(); 2426 UsageStatistics.instance().storageWarning(storageSpace); 2427 2428 // Disable all user interactions, 2429 mCameraAppUI.setDisableAllUserInteractions(true); 2430 } else if (mStorageHint != null) { 2431 mStorageHint.cancel(); 2432 mStorageHint = null; 2433 2434 // Re-enable all user interactions. 2435 mCameraAppUI.setDisableAllUserInteractions(false); 2436 } 2437 } 2438 setResultEx(int resultCode)2439 protected void setResultEx(int resultCode) { 2440 mResultCodeForTesting = resultCode; 2441 setResult(resultCode); 2442 } 2443 setResultEx(int resultCode, Intent data)2444 protected void setResultEx(int resultCode, Intent data) { 2445 mResultCodeForTesting = resultCode; 2446 mResultDataForTesting = data; 2447 setResult(resultCode, data); 2448 } 2449 getResultCode()2450 public int getResultCode() { 2451 return mResultCodeForTesting; 2452 } 2453 getResultData()2454 public Intent getResultData() { 2455 return mResultDataForTesting; 2456 } 2457 isSecureCamera()2458 public boolean isSecureCamera() { 2459 return mSecureCamera; 2460 } 2461 2462 @Override isPaused()2463 public boolean isPaused() { 2464 return mPaused; 2465 } 2466 2467 @Override getPreferredChildModeIndex(int modeIndex)2468 public int getPreferredChildModeIndex(int modeIndex) { 2469 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2470 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager); 2471 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) { 2472 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 2473 } 2474 } 2475 return modeIndex; 2476 } 2477 2478 @Override onModeSelected(int modeIndex)2479 public void onModeSelected(int modeIndex) { 2480 if (mCurrentModeIndex == modeIndex) { 2481 return; 2482 } 2483 2484 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 2485 // Record last used camera mode for quick switching 2486 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 2487 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2488 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2489 Keys.KEY_CAMERA_MODULE_LAST_USED, 2490 modeIndex); 2491 } 2492 2493 closeModule(mCurrentModule); 2494 2495 // Select the correct module index from the mode switcher index. 2496 modeIndex = getPreferredChildModeIndex(modeIndex); 2497 setModuleFromModeIndex(modeIndex); 2498 2499 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 2500 mCameraAppUI.addShutterListener(mCurrentModule); 2501 openModule(mCurrentModule); 2502 // Store the module index so we can use it the next time the Camera 2503 // starts up. 2504 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2505 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex); 2506 } 2507 2508 /** 2509 * Shows the settings dialog. 2510 */ 2511 @Override onSettingsSelected()2512 public void onSettingsSelected() { 2513 UsageStatistics.instance().controlUsed( 2514 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 2515 Intent intent = new Intent(this, CameraSettingsActivity.class); 2516 if (!isKeyguardLocked()) { 2517 startActivity(intent); 2518 } else { 2519 /* Need to explicitly request keyguard dismissal for PIN/pattern 2520 * entry to show up directly. */ 2521 requestDismissKeyguard( 2522 /* requesting Activity: */ CameraActivity.this, 2523 new KeyguardDismissCallback() { 2524 @Override 2525 public void onDismissSucceeded() { 2526 /* Need to use launchActivityByIntent() so that going 2527 * back from settings after unlock leads to main 2528 * activity instead of dismissing camera entirely. */ 2529 launchActivityByIntent(intent); 2530 } 2531 @Override 2532 public void onDismissError() { 2533 Log.e(TAG, "Keyguard dismissal failed."); 2534 } 2535 @Override 2536 public void onDismissCancelled() { 2537 Log.d(TAG, "Keyguard dismissal canceled."); 2538 } 2539 } 2540 ); 2541 } 2542 } 2543 2544 @Override freezeScreenUntilPreviewReady()2545 public void freezeScreenUntilPreviewReady() { 2546 mCameraAppUI.freezeScreenUntilPreviewReady(); 2547 } 2548 2549 @Override getModuleId(int modeIndex)2550 public int getModuleId(int modeIndex) { 2551 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2552 if (agent == null) { 2553 return -1; 2554 } 2555 return agent.getModuleId(); 2556 } 2557 2558 /** 2559 * Sets the mCurrentModuleIndex, creates a new module instance for the given 2560 * index an sets it as mCurrentModule. 2561 */ setModuleFromModeIndex(int modeIndex)2562 private void setModuleFromModeIndex(int modeIndex) { 2563 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2564 if (agent == null) { 2565 return; 2566 } 2567 if (!agent.requestAppForCamera()) { 2568 mCameraController.closeCamera(true); 2569 } 2570 mCurrentModeIndex = agent.getModuleId(); 2571 mCurrentModule = (CameraModule) agent.createModule(this, getIntent()); 2572 } 2573 2574 @Override getSettingsManager()2575 public SettingsManager getSettingsManager() { 2576 return mSettingsManager; 2577 } 2578 2579 @Override getResolutionSetting()2580 public ResolutionSetting getResolutionSetting() { 2581 return mResolutionSetting; 2582 } 2583 2584 @Override getServices()2585 public CameraServices getServices() { 2586 return CameraServicesImpl.instance(); 2587 } 2588 2589 @Override getFatalErrorHandler()2590 public FatalErrorHandler getFatalErrorHandler() { 2591 return mFatalErrorHandler; 2592 } 2593 getSupportedModeNames()2594 public List<String> getSupportedModeNames() { 2595 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 2596 List<String> supported = new ArrayList<String>(); 2597 2598 for (Integer modeIndex : indices) { 2599 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 2600 if (name != null && !name.equals("")) { 2601 supported.add(name); 2602 } 2603 } 2604 return supported; 2605 } 2606 2607 @Override getButtonManager()2608 public ButtonManager getButtonManager() { 2609 if (mButtonManager == null) { 2610 mButtonManager = new ButtonManager(this); 2611 } 2612 return mButtonManager; 2613 } 2614 2615 @Override getSoundPlayer()2616 public SoundPlayer getSoundPlayer() { 2617 return mSoundPlayer; 2618 } 2619 2620 /** 2621 * Launches an ACTION_EDIT intent for the given local data item. If 2622 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2623 * the user start either the tiny planet editor or another photo editor. 2624 * 2625 * @param data The data item to edit. 2626 */ launchEditor(FilmstripItem data)2627 public void launchEditor(FilmstripItem data) { 2628 Intent intent = new Intent(Intent.ACTION_EDIT) 2629 .setDataAndType(data.getData().getUri(), data.getData().getMimeType()) 2630 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2631 try { 2632 launchActivityByIntent(intent); 2633 } catch (ActivityNotFoundException e) { 2634 final String msgEditWith = getResources().getString(R.string.edit_with); 2635 launchActivityByIntent(Intent.createChooser(intent, msgEditWith)); 2636 } 2637 } 2638 2639 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)2640 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2641 super.onCreateContextMenu(menu, v, menuInfo); 2642 2643 MenuInflater inflater = getMenuInflater(); 2644 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2645 } 2646 2647 @Override onContextItemSelected(MenuItem item)2648 public boolean onContextItemSelected(MenuItem item) { 2649 switch (item.getItemId()) { 2650 case R.id.tiny_planet_editor: 2651 mMyFilmstripBottomControlListener.onTinyPlanet(); 2652 return true; 2653 case R.id.photo_editor: 2654 mMyFilmstripBottomControlListener.onEdit(); 2655 return true; 2656 } 2657 return false; 2658 } 2659 2660 /** 2661 * Launch the tiny planet editor. 2662 * 2663 * @param data The data must be a 360 degree stereographically mapped 2664 * panoramic image. It will not be modified, instead a new item 2665 * with the result will be added to the filmstrip. 2666 */ launchTinyPlanetEditor(FilmstripItem data)2667 public void launchTinyPlanetEditor(FilmstripItem data) { 2668 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2669 Bundle bundle = new Bundle(); 2670 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString()); 2671 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle()); 2672 fragment.setArguments(bundle); 2673 fragment.show(getFragmentManager(), "tiny_planet"); 2674 } 2675 2676 /** 2677 * Returns what UI mode (capture mode or filmstrip) we are in. 2678 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2679 */ currentUserInterfaceMode()2680 private int currentUserInterfaceMode() { 2681 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2682 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2683 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2684 } 2685 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2686 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2687 } 2688 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2689 mode = NavigationChange.Mode.LENS_BLUR; 2690 } 2691 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2692 mode = NavigationChange.Mode.HDR_PLUS; 2693 } 2694 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2695 mode = NavigationChange.Mode.PHOTO_SPHERE; 2696 } 2697 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2698 mode = NavigationChange.Mode.PANORAMA; 2699 } 2700 if (mFilmstripVisible) { 2701 mode = NavigationChange.Mode.FILMSTRIP; 2702 } 2703 return mode; 2704 } 2705 openModule(CameraModule module)2706 private void openModule(CameraModule module) { 2707 module.init(this, isSecureCamera(), isCaptureIntent()); 2708 module.hardResetSettings(mSettingsManager); 2709 // Hide accessibility zoom UI by default. Modules will enable it themselves if required. 2710 getCameraAppUI().hideAccessibilityZoomUI(); 2711 if (!mPaused) { 2712 module.resume(); 2713 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2714 NavigationChange.InteractionCause.BUTTON); 2715 updatePreviewVisibility(); 2716 } 2717 } 2718 closeModule(CameraModule module)2719 private void closeModule(CameraModule module) { 2720 module.pause(); 2721 mCameraAppUI.clearModuleUI(); 2722 } 2723 performDeletion()2724 private void performDeletion() { 2725 if (!mPendingDeletion) { 2726 return; 2727 } 2728 hideUndoDeletionBar(false); 2729 mDataAdapter.executeDeletion(); 2730 } 2731 showUndoDeletionBar()2732 public void showUndoDeletionBar() { 2733 if (mPendingDeletion) { 2734 performDeletion(); 2735 } 2736 Log.v(TAG, "showing undo bar"); 2737 mPendingDeletion = true; 2738 if (mUndoDeletionBar == null) { 2739 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2740 mAboveFilmstripControlLayout, true); 2741 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2742 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2743 button.setOnClickListener(new View.OnClickListener() { 2744 @Override 2745 public void onClick(View view) { 2746 mDataAdapter.undoDeletion(); 2747 // Fix for b/21666018: When undoing a delete in Fullscreen 2748 // mode, just flip 2749 // back to the filmstrip to force a refresh. 2750 if (mFilmstripController.inFullScreen()) { 2751 mFilmstripController.goToFilmstrip(); 2752 } 2753 hideUndoDeletionBar(true); 2754 } 2755 }); 2756 // Setting undo bar clickable to avoid touch events going through 2757 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2758 mUndoDeletionBar.setClickable(true); 2759 // When there is user interaction going on with the undo button, we 2760 // do not want to hide the undo bar. 2761 button.setOnTouchListener(new View.OnTouchListener() { 2762 @Override 2763 public boolean onTouch(View v, MotionEvent event) { 2764 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2765 mIsUndoingDeletion = true; 2766 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2767 mIsUndoingDeletion = false; 2768 } 2769 return false; 2770 } 2771 }); 2772 } 2773 mUndoDeletionBar.setAlpha(0f); 2774 mUndoDeletionBar.setVisibility(View.VISIBLE); 2775 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2776 } 2777 hideUndoDeletionBar(boolean withAnimation)2778 private void hideUndoDeletionBar(boolean withAnimation) { 2779 Log.v(TAG, "Hiding undo deletion bar"); 2780 mPendingDeletion = false; 2781 if (mUndoDeletionBar != null) { 2782 if (withAnimation) { 2783 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2784 .setListener(new Animator.AnimatorListener() { 2785 @Override 2786 public void onAnimationStart(Animator animation) { 2787 // Do nothing. 2788 } 2789 2790 @Override 2791 public void onAnimationEnd(Animator animation) { 2792 mUndoDeletionBar.setVisibility(View.GONE); 2793 } 2794 2795 @Override 2796 public void onAnimationCancel(Animator animation) { 2797 // Do nothing. 2798 } 2799 2800 @Override 2801 public void onAnimationRepeat(Animator animation) { 2802 // Do nothing. 2803 } 2804 }).start(); 2805 } else { 2806 mUndoDeletionBar.setVisibility(View.GONE); 2807 } 2808 } 2809 } 2810 2811 /** 2812 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2813 * capture intent. 2814 * 2815 * @param enable {@code true} to enable swipe. 2816 */ setSwipingEnabled(boolean enable)2817 public void setSwipingEnabled(boolean enable) { 2818 // TODO: Bring back the functionality. 2819 if (isCaptureIntent()) { 2820 // lockPreview(true); 2821 } else { 2822 // lockPreview(!enable); 2823 } 2824 } 2825 2826 // Accessor methods for getting latency times used in performance testing getFirstPreviewTime()2827 public long getFirstPreviewTime() { 2828 if (mCurrentModule instanceof PhotoModule) { 2829 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2830 if (coverHiddenTime != -1) { 2831 return coverHiddenTime - mOnCreateTime; 2832 } 2833 } 2834 return -1; 2835 } 2836 getAutoFocusTime()2837 public long getAutoFocusTime() { 2838 return (mCurrentModule instanceof PhotoModule) ? 2839 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2840 } 2841 getShutterLag()2842 public long getShutterLag() { 2843 return (mCurrentModule instanceof PhotoModule) ? 2844 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2845 } 2846 getShutterToPictureDisplayedTime()2847 public long getShutterToPictureDisplayedTime() { 2848 return (mCurrentModule instanceof PhotoModule) ? 2849 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2850 } 2851 getPictureDisplayedToJpegCallbackTime()2852 public long getPictureDisplayedToJpegCallbackTime() { 2853 return (mCurrentModule instanceof PhotoModule) ? 2854 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2855 } 2856 getJpegCallbackFinishTime()2857 public long getJpegCallbackFinishTime() { 2858 return (mCurrentModule instanceof PhotoModule) ? 2859 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2860 } 2861 getCaptureStartTime()2862 public long getCaptureStartTime() { 2863 return (mCurrentModule instanceof PhotoModule) ? 2864 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2865 } 2866 isRecording()2867 public boolean isRecording() { 2868 return (mCurrentModule instanceof VideoModule) ? 2869 ((VideoModule) mCurrentModule).isRecording() : false; 2870 } 2871 getCameraOpenErrorCallback()2872 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() { 2873 return mCameraController; 2874 } 2875 2876 // For debugging purposes only. getCurrentModule()2877 public CameraModule getCurrentModule() { 2878 return mCurrentModule; 2879 } 2880 2881 @Override showTutorial(AbstractTutorialOverlay tutorial)2882 public void showTutorial(AbstractTutorialOverlay tutorial) { 2883 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2884 } 2885 2886 @Override finishActivityWithIntentCompleted(Intent resultIntent)2887 public void finishActivityWithIntentCompleted(Intent resultIntent) { 2888 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent); 2889 } 2890 2891 @Override finishActivityWithIntentCanceled()2892 public void finishActivityWithIntentCanceled() { 2893 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent()); 2894 } 2895 finishActivityWithIntentResult(int resultCode, Intent resultIntent)2896 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) { 2897 mResultCodeForTesting = resultCode; 2898 mResultDataForTesting = resultIntent; 2899 setResult(resultCode, resultIntent); 2900 finish(); 2901 } 2902 keepScreenOnForAWhile()2903 private void keepScreenOnForAWhile() { 2904 if (mKeepScreenOn) { 2905 return; 2906 } 2907 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2908 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2909 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2910 } 2911 resetScreenOn()2912 private void resetScreenOn() { 2913 mKeepScreenOn = false; 2914 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2915 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2916 } 2917 2918 /** 2919 * @return {@code true} if the Gallery is launched successfully. 2920 */ startGallery()2921 private boolean startGallery() { 2922 if (mGalleryIntent == null) { 2923 return false; 2924 } 2925 try { 2926 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2927 NavigationChange.InteractionCause.BUTTON); 2928 Intent startGalleryIntent = new Intent(mGalleryIntent); 2929 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 2930 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex); 2931 if (currentFilmstripItem != null) { 2932 GalleryHelper.setContentUri(startGalleryIntent, 2933 currentFilmstripItem.getData().getUri()); 2934 } 2935 launchActivityByIntent(startGalleryIntent); 2936 } catch (ActivityNotFoundException e) { 2937 Log.w(TAG, "Failed to launch gallery activity, closing"); 2938 } 2939 return false; 2940 } 2941 setNfcBeamPushUriFromData(FilmstripItem data)2942 private void setNfcBeamPushUriFromData(FilmstripItem data) { 2943 final Uri uri = data.getData().getUri(); 2944 if (uri != Uri.EMPTY) { 2945 mNfcPushUris[0] = uri; 2946 } else { 2947 mNfcPushUris[0] = null; 2948 } 2949 } 2950 2951 /** 2952 * Updates the visibility of the filmstrip bottom controls and action bar. 2953 */ updateUiByData(final int index)2954 private void updateUiByData(final int index) { 2955 final FilmstripItem currentData = mDataAdapter.getItemAt(index); 2956 if (currentData == null) { 2957 Log.w(TAG, "Current data ID not found."); 2958 hideSessionProgress(); 2959 return; 2960 } 2961 updateActionBarMenu(currentData); 2962 2963 /* Bottom controls. */ 2964 updateBottomControlsByData(currentData); 2965 2966 if (isSecureCamera()) { 2967 // We cannot show buttons in secure camera since go to other 2968 // activities might create a security hole. 2969 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2970 return; 2971 } 2972 2973 setNfcBeamPushUriFromData(currentData); 2974 2975 if (!mDataAdapter.isMetadataUpdatedAt(index)) { 2976 mDataAdapter.updateMetadataAt(index); 2977 } 2978 } 2979 2980 /** 2981 * Updates the bottom controls based on the data. 2982 */ updateBottomControlsByData(final FilmstripItem currentData)2983 private void updateBottomControlsByData(final FilmstripItem currentData) { 2984 2985 final CameraAppUI.BottomPanel filmstripBottomPanel = 2986 mCameraAppUI.getFilmstripBottomControls(); 2987 filmstripBottomPanel.showControls(); 2988 filmstripBottomPanel.setEditButtonVisibility( 2989 currentData.getAttributes().canEdit()); 2990 filmstripBottomPanel.setShareButtonVisibility( 2991 currentData.getAttributes().canShare()); 2992 filmstripBottomPanel.setDeleteButtonVisibility( 2993 currentData.getAttributes().canDelete()); 2994 2995 /* Progress bar */ 2996 2997 Uri contentUri = currentData.getData().getUri(); 2998 CaptureSessionManager sessionManager = getServices() 2999 .getCaptureSessionManager(); 3000 3001 if (sessionManager.hasErrorMessage(contentUri)) { 3002 showProcessError(sessionManager.getErrorMessageId(contentUri)); 3003 } else { 3004 filmstripBottomPanel.hideProgressError(); 3005 hideSessionProgress(); 3006 } 3007 3008 /* View button */ 3009 3010 // We need to add this to a separate DB. 3011 final int viewButtonVisibility; 3012 if (currentData.getMetadata().isUsePanoramaViewer()) { 3013 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 3014 } else if (currentData.getMetadata().isHasRgbzData()) { 3015 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 3016 } else { 3017 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 3018 } 3019 3020 filmstripBottomPanel.setTinyPlanetEnabled( 3021 currentData.getMetadata().isPanorama360()); 3022 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 3023 } 3024 showDetailsDialog(int index)3025 private void showDetailsDialog(int index) { 3026 final FilmstripItem data = mDataAdapter.getItemAt(index); 3027 if (data == null) { 3028 return; 3029 } 3030 Optional<MediaDetails> details = data.getMediaDetails(); 3031 if (!details.isPresent()) { 3032 return; 3033 } 3034 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get()); 3035 detailDialog.show(); 3036 UsageStatistics.instance().mediaInteraction( 3037 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS, 3038 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index)); 3039 } 3040 3041 /** 3042 * Show or hide action bar items depending on current data type. 3043 */ updateActionBarMenu(FilmstripItem data)3044 private void updateActionBarMenu(FilmstripItem data) { 3045 if (mActionBarMenu == null) { 3046 return; 3047 } 3048 3049 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 3050 if (detailsMenuItem == null) { 3051 return; 3052 } 3053 3054 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo(); 3055 detailsMenuItem.setVisible(showDetails); 3056 } 3057 } 3058