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(CameraActivity.this, 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 default: 1363 return super.onOptionsItemSelected(item); 1364 } 1365 } 1366 isCaptureIntent()1367 private boolean isCaptureIntent() { 1368 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 1369 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 1370 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1371 return true; 1372 } else { 1373 return false; 1374 } 1375 } 1376 1377 /** 1378 * Note: Make sure this callback is unregistered properly when the activity 1379 * is destroyed since we're otherwise leaking the Activity reference. 1380 */ 1381 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback 1382 = new CameraExceptionHandler.CameraExceptionCallback() { 1383 @Override 1384 public void onCameraError(int errorCode) { 1385 // Not a fatal error. only do Log.e(). 1386 Log.e(TAG, "Camera error callback. error=" + errorCode); 1387 } 1388 @Override 1389 public void onCameraException( 1390 RuntimeException ex, String commandHistory, int action, int state) { 1391 Log.e(TAG, "Camera Exception", ex); 1392 UsageStatistics.instance().cameraFailure( 1393 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION, 1394 commandHistory, action, state); 1395 onFatalError(); 1396 } 1397 @Override 1398 public void onDispatchThreadException(RuntimeException ex) { 1399 Log.e(TAG, "DispatchThread Exception", ex); 1400 UsageStatistics.instance().cameraFailure( 1401 eventprotos.CameraFailure.FailureReason.API_TIMEOUT, 1402 null, UsageStatistics.NONE, UsageStatistics.NONE); 1403 onFatalError(); 1404 } 1405 private void onFatalError() { 1406 if (mCameraFatalError) { 1407 return; 1408 } 1409 mCameraFatalError = true; 1410 1411 // If the activity receives exception during onPause, just exit the app. 1412 if (mPaused && !isFinishing()) { 1413 Log.e(TAG, "Fatal error during onPause, call Activity.finish()"); 1414 finish(); 1415 } else { 1416 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA); 1417 } 1418 } 1419 }; 1420 1421 @Override onNewIntentTasks(Intent intent)1422 public void onNewIntentTasks(Intent intent) { 1423 onModeSelected(getModeIndex()); 1424 } 1425 1426 @Override onCreateTasks(Bundle state)1427 public void onCreateTasks(Bundle state) { 1428 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start(); 1429 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START); 1430 mOnCreateTime = System.currentTimeMillis(); 1431 mAppContext = getApplicationContext(); 1432 mMainHandler = new MainHandler(this, getMainLooper()); 1433 mLocationManager = new LocationManager(mAppContext, shouldUseNoOpLocation()); 1434 mOrientationManager = new OrientationManagerImpl(this, mMainHandler); 1435 mSettingsManager = getServices().getSettingsManager(); 1436 mSoundPlayer = new SoundPlayer(mAppContext); 1437 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(), 1438 getServices().getMemoryManager()); 1439 mFatalErrorHandler = new FatalErrorHandlerImpl(this); 1440 checkPermissions(); 1441 if (!mHasCriticalPermissions) { 1442 Log.v(TAG, "onCreate: Missing critical permissions."); 1443 finish(); 1444 return; 1445 } 1446 profile.mark(); 1447 if (!Glide.isSetup()) { 1448 Context context = getAndroidContext(); 1449 Glide.setup(new GlideBuilder(context) 1450 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888) 1451 .setResizeService(new FifoPriorityThreadPoolExecutor(2))); 1452 1453 Glide glide = Glide.get(context); 1454 1455 // As a camera we will use a large amount of memory 1456 // for displaying images. 1457 glide.setMemoryCategory(MemoryCategory.HIGH); 1458 } 1459 profile.mark("Glide.setup"); 1460 1461 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance(); 1462 try { 1463 mOneCameraOpener = OneCameraModule.provideOneCameraOpener( 1464 mFeatureConfig, 1465 mAppContext, 1466 mActiveCameraDeviceTracker, 1467 ResolutionUtil.getDisplayMetrics(this)); 1468 mOneCameraManager = OneCameraModule.provideOneCameraManager(); 1469 } catch (OneCameraException e) { 1470 // Log error and continue start process while showing error dialog.. 1471 Log.e(TAG, "Creating camera manager failed.", e); 1472 mFatalErrorHandler.onGenericCameraAccessFailure(); 1473 } 1474 profile.mark("OneCameraManager.get"); 1475 1476 try { 1477 mCameraController = new CameraController(mAppContext, this, mMainHandler, 1478 CameraAgentFactory.getAndroidCameraAgent(mAppContext, 1479 CameraAgentFactory.CameraApi.API_1), 1480 CameraAgentFactory.getAndroidCameraAgent(mAppContext, 1481 CameraAgentFactory.CameraApi.AUTO), 1482 mActiveCameraDeviceTracker); 1483 mCameraController.setCameraExceptionHandler( 1484 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler)); 1485 } catch (AssertionError e) { 1486 Log.e(TAG, "Creating camera controller failed.", e); 1487 mFatalErrorHandler.onGenericCameraAccessFailure(); 1488 } 1489 1490 // TODO: Try to move all the resources allocation to happen as soon as 1491 // possible so we can call module.init() at the earliest time. 1492 mModuleManager = new ModuleManagerImpl(); 1493 1494 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig); 1495 1496 AppUpgrader appUpgrader = new AppUpgrader(this); 1497 appUpgrader.upgrade(mSettingsManager); 1498 1499 // Make sure the picture sizes are correctly cached for the current OS 1500 // version. 1501 profile.mark(); 1502 try { 1503 PictureSizeLoader pictureSizeLoader = new PictureSizeLoader(mAppContext); 1504 pictureSizeLoader.computePictureSizes(); 1505 pictureSizeLoader.release(); 1506 } catch (AssertionError e) { 1507 Log.e(TAG, "Creating camera controller failed.", e); 1508 mFatalErrorHandler.onGenericCameraAccessFailure(); 1509 } 1510 profile.mark("computePictureSizes"); 1511 Keys.setDefaults(mSettingsManager, mAppContext); 1512 1513 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager, 1514 getContentResolver()); 1515 1516 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 1517 // We suppress this flag via theme when drawing the system preview 1518 // background, but once we create activity here, reactivate to the 1519 // default value. The default is important for L, we don't want to 1520 // change app behavior, just starting background drawable layout. 1521 if (ApiHelper.isLOrHigher()) { 1522 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 1523 } 1524 1525 profile.mark(); 1526 setContentView(R.layout.activity_main); 1527 profile.mark("setContentView()"); 1528 // A window background is set in styles.xml for the system to show a 1529 // drawable background with gray color and camera icon before the 1530 // activity is created. We set the background to null here to prevent 1531 // overdraw, all views must take care of drawing backgrounds if 1532 // necessary. This call to setBackgroundDrawable must occur after 1533 // setContentView, otherwise a background may be set again from the 1534 // style. 1535 getWindow().setBackgroundDrawable(null); 1536 1537 mActionBar = getActionBar(); 1538 // set actionbar background to 100% or 50% transparent 1539 if (ApiHelper.isLOrHigher()) { 1540 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000)); 1541 } else { 1542 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000)); 1543 } 1544 1545 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); 1546 mModeListView.init(mModuleManager.getSupportedModeIndexList()); 1547 if (ApiHelper.HAS_ROTATION_ANIMATION) { 1548 setRotationAnimation(); 1549 } 1550 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() { 1551 @Override 1552 public void onVisibilityChanged(boolean visible) { 1553 mModeListVisible = visible; 1554 mCameraAppUI.setShutterButtonImportantToA11y(!visible); 1555 updatePreviewVisibility(); 1556 } 1557 }); 1558 1559 // Check if this is in the secure camera mode. 1560 Intent intent = getIntent(); 1561 String action = intent.getAction(); 1562 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 1563 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 1564 mSecureCamera = true; 1565 } else { 1566 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 1567 } 1568 1569 if (mSecureCamera) { 1570 // Change the window flags so that secure camera can show when 1571 // locked 1572 Window win = getWindow(); 1573 WindowManager.LayoutParams params = win.getAttributes(); 1574 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 1575 win.setAttributes(params); 1576 1577 // Filter for screen off so that we can finish secure camera 1578 // activity when screen is off. 1579 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF); 1580 registerReceiver(mShutdownReceiver, filter_screen_off); 1581 1582 // Filter for phone unlock so that we can finish secure camera 1583 // via this UI path: 1584 // 1. from secure lock screen, user starts secure camera 1585 // 2. user presses home button 1586 // 3. user unlocks phone 1587 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT); 1588 registerReceiver(mShutdownReceiver, filter_user_unlock); 1589 } 1590 mCameraAppUI = new CameraAppUI(this, 1591 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent()); 1592 1593 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener); 1594 1595 mAboveFilmstripControlLayout = 1596 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout); 1597 1598 // Add the session listener so we can track the session progress 1599 // updates. 1600 getServices().getCaptureSessionManager().addSessionListener(mSessionListener); 1601 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController(); 1602 mFilmstripController.setImageGap( 1603 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 1604 profile.mark("Configure Camera UI"); 1605 1606 mPanoramaViewHelper = new PanoramaViewHelper(this); 1607 mPanoramaViewHelper.onCreate(); 1608 1609 ContentResolver appContentResolver = mAppContext.getContentResolver(); 1610 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext); 1611 mPhotoItemFactory = new PhotoItemFactory(CameraActivity.this, glideManager, appContentResolver, 1612 new PhotoDataFactory()); 1613 mVideoItemFactory = new VideoItemFactory(CameraActivity.this, glideManager, appContentResolver, 1614 new VideoDataFactory()); 1615 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener); 1616 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1617 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 1618 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS); 1619 } 1620 1621 setModuleFromModeIndex(getModeIndex()); 1622 1623 profile.mark(); 1624 mCameraAppUI.prepareModuleUI(); 1625 profile.mark("Init Current Module UI"); 1626 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent()); 1627 profile.mark("Init CurrentModule"); 1628 1629 preloadFilmstripItems(); 1630 1631 // Candidate for deletion as Android Beam is deprecated in Android Q 1632 setupNfcBeamPush(); 1633 1634 mLocalImagesObserver = new FilmstripContentObserver(); 1635 mLocalVideosObserver = new FilmstripContentObserver(); 1636 1637 getContentResolver().registerContentObserver( 1638 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1639 mLocalImagesObserver); 1640 getContentResolver().registerContentObserver( 1641 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1642 mLocalVideosObserver); 1643 1644 mMemoryManager = getServices().getMemoryManager(); 1645 1646 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1647 @Override 1648 public void run() { 1649 HashMap memoryData = mMemoryManager.queryMemory(); 1650 UsageStatistics.instance().reportMemoryConsumed(memoryData, 1651 MemoryQuery.REPORT_LABEL_LAUNCH); 1652 } 1653 }); 1654 1655 mMotionManager = getServices().getMotionManager(); 1656 1657 mFirstRunDialog = new FirstRunDialog(this, 1658 this /* as context */, 1659 mResolutionSetting, 1660 mSettingsManager, 1661 mOneCameraManager, 1662 new FirstRunDialog.FirstRunDialogListener() { 1663 @Override 1664 public void onFirstRunStateReady() { 1665 // Run normal resume tasks. 1666 resume(); 1667 } 1668 1669 @Override 1670 public void onFirstRunDialogCancelled() { 1671 // App isn't functional until users finish first run dialog. 1672 // We need to finish here since users hit back button during 1673 // first run dialog (b/19593942). 1674 finish(); 1675 } 1676 1677 @Override 1678 public void onCameraAccessException() { 1679 mFatalErrorHandler.onGenericCameraAccessFailure(); 1680 } 1681 }); 1682 profile.stop(); 1683 } 1684 1685 /** 1686 * Get the current mode index from the Intent or from persistent 1687 * settings. 1688 */ getModeIndex()1689 private int getModeIndex() { 1690 int modeIndex = -1; 1691 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo); 1692 int videoIndex = getResources().getInteger(R.integer.camera_mode_video); 1693 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1694 int captureIntentIndex = 1695 getResources().getInteger(R.integer.camera_mode_capture_intent); 1696 String intentAction = getIntent().getAction(); 1697 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction) 1698 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) { 1699 modeIndex = videoIndex; 1700 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction) 1701 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) { 1702 // Capture intent. 1703 modeIndex = captureIntentIndex; 1704 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction) 1705 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction) 1706 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) { 1707 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1708 Keys.KEY_CAMERA_MODULE_LAST_USED); 1709 1710 // For upgraders who have not seen the aspect ratio selection screen, 1711 // we need to drop them back in the photo module and have them select 1712 // aspect ratio. 1713 // TODO: Move this to SettingsManager as an upgrade procedure. 1714 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1715 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { 1716 modeIndex = photoIndex; 1717 } 1718 } else { 1719 // If the activity has not been started using an explicit intent, 1720 // read the module index from the last time the user changed modes 1721 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1722 Keys.KEY_STARTUP_MODULE_INDEX); 1723 if ((modeIndex == gcamIndex && 1724 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) { 1725 modeIndex = photoIndex; 1726 } 1727 } 1728 return modeIndex; 1729 } 1730 1731 /** 1732 * Incase the calling package doesn't have ACCESS_FINE_LOCATION permissions, we should not pass 1733 * it valid location information in exif. 1734 */ shouldUseNoOpLocation()1735 private boolean shouldUseNoOpLocation () { 1736 String callingPackage = getCallingPackage(); 1737 if (callingPackage == null) { 1738 // Activity not started through startActivityForResult. 1739 return false; 1740 } 1741 PackageInfo packageInfo = null; 1742 try { 1743 packageInfo = getPackageManager().getPackageInfo(callingPackage, 1744 PackageManager.GET_PERMISSIONS); 1745 } catch (Exception e) { 1746 Log.w(TAG, "Unable to get PackageInfo for callingPackage " + callingPackage); 1747 } 1748 if (packageInfo != null) { 1749 if (packageInfo.requestedPermissions == null) { 1750 // No-permissions at all, were requested by the calling app. 1751 return true; 1752 } 1753 for (int i = 0; i < packageInfo.requestedPermissions.length; i++) { 1754 if (packageInfo.requestedPermissions[i].equals( 1755 Manifest.permission.ACCESS_FINE_LOCATION) && 1756 (packageInfo.requestedPermissionsFlags[i] & 1757 PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { 1758 return false; 1759 } 1760 } 1761 } 1762 return true; 1763 } 1764 /** 1765 * Call this whenever the mode drawer or filmstrip change the visibility 1766 * state. 1767 */ updatePreviewVisibility()1768 private void updatePreviewVisibility() { 1769 if (mCurrentModule == null) { 1770 return; 1771 } 1772 1773 int visibility = getPreviewVisibility(); 1774 mCameraAppUI.onPreviewVisiblityChanged(visibility); 1775 updatePreviewRendering(visibility); 1776 mCurrentModule.onPreviewVisibilityChanged(visibility); 1777 } 1778 updatePreviewRendering(int visibility)1779 private void updatePreviewRendering(int visibility) { 1780 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1781 mCameraAppUI.pausePreviewRendering(); 1782 } else { 1783 mCameraAppUI.resumePreviewRendering(); 1784 } 1785 } 1786 getPreviewVisibility()1787 private int getPreviewVisibility() { 1788 if (mFilmstripCoversPreview) { 1789 return ModuleController.VISIBILITY_HIDDEN; 1790 } else if (mModeListVisible){ 1791 return ModuleController.VISIBILITY_COVERED; 1792 } else { 1793 return ModuleController.VISIBILITY_VISIBLE; 1794 } 1795 } 1796 setRotationAnimation()1797 private void setRotationAnimation() { 1798 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1799 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1800 Window win = getWindow(); 1801 WindowManager.LayoutParams winParams = win.getAttributes(); 1802 winParams.rotationAnimation = rotationAnimation; 1803 win.setAttributes(winParams); 1804 } 1805 1806 @Override onUserInteraction()1807 public void onUserInteraction() { 1808 super.onUserInteraction(); 1809 if (!isFinishing()) { 1810 keepScreenOnForAWhile(); 1811 } 1812 } 1813 1814 @Override dispatchTouchEvent(MotionEvent ev)1815 public boolean dispatchTouchEvent(MotionEvent ev) { 1816 boolean result = super.dispatchTouchEvent(ev); 1817 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1818 // Real deletion is postponed until the next user interaction after 1819 // the gesture that triggers deletion. Until real deletion is 1820 // performed, users can click the undo button to bring back the 1821 // image that they chose to delete. 1822 if (mPendingDeletion && !mIsUndoingDeletion) { 1823 performDeletion(); 1824 } 1825 } 1826 return result; 1827 } 1828 1829 @Override onPauseTasks()1830 public void onPauseTasks() { 1831 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE); 1832 Profile profile = mProfiler.create("CameraActivity.onPause").start(); 1833 1834 /* 1835 * Save the last module index after all secure camera and icon launches, 1836 * not just on mode switches. 1837 * 1838 * Right now we exclude capture intents from this logic, because we also 1839 * ignore the cross-Activity recovery logic in onStart for capture intents. 1840 */ 1841 if (!isCaptureIntent()) { 1842 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 1843 Keys.KEY_STARTUP_MODULE_INDEX, 1844 mCurrentModeIndex); 1845 } 1846 1847 mPaused = true; 1848 mCameraAppUI.hideCaptureIndicator(); 1849 mFirstRunDialog.dismiss(); 1850 1851 // Delete photos that are pending deletion 1852 performDeletion(); 1853 mCurrentModule.pause(); 1854 mOrientationManager.pause(); 1855 mPanoramaViewHelper.onPause(); 1856 1857 mLocalImagesObserver.setForegroundChangeListener(null); 1858 mLocalImagesObserver.setActivityPaused(true); 1859 mLocalVideosObserver.setActivityPaused(true); 1860 if (mPreloader != null) { 1861 mPreloader.cancelAllLoads(); 1862 } 1863 resetScreenOn(); 1864 1865 mMotionManager.stop(); 1866 1867 // Always stop recording location when paused. Resume will start 1868 // location recording again if the location setting is on. 1869 mLocationManager.recordLocation(false); 1870 1871 UsageStatistics.instance().backgrounded(); 1872 1873 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home 1874 // button. Let's just kill the process. 1875 if (mCameraFatalError && !isFinishing()) { 1876 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()"); 1877 finish(); 1878 } else { 1879 // Close the camera and wait for the operation done. 1880 Log.v(TAG, "onPause closing camera"); 1881 if (mCameraController != null) { 1882 mCameraController.closeCamera(true); 1883 } 1884 } 1885 1886 profile.stop(); 1887 } 1888 1889 @Override onResumeTasks()1890 public void onResumeTasks() { 1891 mPaused = false; 1892 checkPermissions(); 1893 if (!mHasCriticalPermissions) { 1894 Log.v(TAG, "onResume: Missing critical permissions."); 1895 finish(); 1896 return; 1897 } 1898 if (!isSecureCamera() && !isCaptureIntent()) { 1899 // Show the dialog if necessary. The rest resume logic will be invoked 1900 // at the onFirstRunStateReady() callback. 1901 try { 1902 mFirstRunDialog.showIfNecessary(); 1903 } catch (AssertionError e) { 1904 Log.e(TAG, "Creating camera controller failed.", e); 1905 mFatalErrorHandler.onGenericCameraAccessFailure(); 1906 } 1907 } else { 1908 // In secure mode from lockscreen, we go straight to camera and will 1909 // show first run dialog next time user enters launcher. 1910 Log.v(TAG, "in secure mode, skipping first run dialog check"); 1911 resume(); 1912 } 1913 } 1914 1915 /** 1916 * Checks if any of the needed Android runtime permissions are missing. 1917 * If they are, then launch the permissions activity under one of the following conditions: 1918 * a) The permissions dialogs have not run yet. We will ask for permission only once. 1919 * b) If the missing permissions are critical to the app running, we will display a fatal error dialog. 1920 * Critical permissions are: camera, microphone and storage. The app cannot run without them. 1921 * Non-critical permission is location. 1922 */ checkPermissions()1923 private void checkPermissions() { 1924 if (!ApiHelper.isMOrHigher()) { 1925 Log.v(TAG, "not running on M, skipping permission checks"); 1926 mHasCriticalPermissions = true; 1927 return; 1928 } 1929 1930 if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && 1931 checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { 1932 mHasCriticalPermissions = true; 1933 } else { 1934 mHasCriticalPermissions = false; 1935 } 1936 if (!mHasCriticalPermissions || (mSettingsManager.getBoolean( 1937 SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION) && 1938 (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) 1939 != PackageManager.PERMISSION_GRANTED) && 1940 !mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1941 Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS))) { 1942 // TODO: Convert PermissionsActivity into a dialog so we 1943 // don't lose the state of CameraActivity. 1944 Intent intent = new Intent(this, PermissionsActivity.class); 1945 startActivity(intent); 1946 finish(); 1947 } 1948 } 1949 preloadFilmstripItems()1950 private void preloadFilmstripItems() { 1951 if (mDataAdapter == null) { 1952 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext, 1953 mPhotoItemFactory, mVideoItemFactory); 1954 mDataAdapter.setLocalDataListener(mFilmstripItemListener); 1955 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter, 1956 mDataAdapter); 1957 if (!mSecureCamera) { 1958 mFilmstripController.setDataAdapter(mDataAdapter); 1959 if (!isCaptureIntent()) { 1960 mDataAdapter.requestLoad(new Callback<Void>() { 1961 @Override 1962 public void onCallback(Void result) { 1963 fillTemporarySessions(); 1964 } 1965 }); 1966 } 1967 } else { 1968 // Put a lock placeholder as the last image by setting its date to 1969 // 0. 1970 ImageView v = (ImageView) getLayoutInflater().inflate( 1971 R.layout.secure_album_placeholder, null); 1972 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal()); 1973 v.setOnClickListener(new View.OnClickListener() { 1974 @Override 1975 public void onClick(View view) { 1976 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 1977 NavigationChange.InteractionCause.BUTTON); 1978 startGallery(); 1979 finish(); 1980 } 1981 }); 1982 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera)); 1983 mDataAdapter = new FixedLastProxyAdapter( 1984 mAppContext, 1985 mDataAdapter, 1986 new PlaceholderItem( 1987 v, 1988 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER, 1989 v.getDrawable().getIntrinsicWidth(), 1990 v.getDrawable().getIntrinsicHeight())); 1991 // Flush out all the original data. 1992 mDataAdapter.clear(); 1993 mFilmstripController.setDataAdapter(mDataAdapter); 1994 } 1995 } 1996 } 1997 resume()1998 private void resume() { 1999 Profile profile = mProfiler.create("CameraActivity.resume").start(); 2000 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME); 2001 Log.v(TAG, "Build info: " + Build.DISPLAY); 2002 updateStorageSpaceAndHint(null); 2003 2004 mLastLayoutOrientation = getResources().getConfiguration().orientation; 2005 2006 // TODO: Handle this in OrientationManager. 2007 // Auto-rotate off 2008 if (Settings.System.getInt(getContentResolver(), 2009 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 2010 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 2011 mAutoRotateScreen = false; 2012 } else { 2013 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 2014 mAutoRotateScreen = true; 2015 } 2016 2017 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and 2018 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to 2019 // lockscreen onResume->onPause->onResume sequence. 2020 int source; 2021 String action = getIntent().getAction(); 2022 if (action == null) { 2023 source = ForegroundSource.UNKNOWN_SOURCE; 2024 } else { 2025 switch (action) { 2026 case MediaStore.ACTION_IMAGE_CAPTURE: 2027 source = ForegroundSource.ACTION_IMAGE_CAPTURE; 2028 break; 2029 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA: 2030 // was UNKNOWN_SOURCE in Fishlake. 2031 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA; 2032 break; 2033 case MediaStore.INTENT_ACTION_VIDEO_CAMERA: 2034 // was UNKNOWN_SOURCE in Fishlake. 2035 source = ForegroundSource.ACTION_VIDEO_CAMERA; 2036 break; 2037 case MediaStore.ACTION_VIDEO_CAPTURE: 2038 source = ForegroundSource.ACTION_VIDEO_CAPTURE; 2039 break; 2040 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE: 2041 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake. 2042 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE; 2043 break; 2044 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE: 2045 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE; 2046 break; 2047 case Intent.ACTION_MAIN: 2048 source = ForegroundSource.ACTION_MAIN; 2049 break; 2050 default: 2051 source = ForegroundSource.UNKNOWN_SOURCE; 2052 break; 2053 } 2054 } 2055 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(), 2056 isKeyguardSecure(), isKeyguardLocked(), 2057 mStartupOnCreate, mExecutionStartNanoTime); 2058 2059 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext); 2060 if (ApiHelper.isLOrHigher()) { 2061 // hide the up affordance for L devices, it's not very Materially 2062 mActionBar.setDisplayShowHomeEnabled(false); 2063 } 2064 2065 mOrientationManager.resume(); 2066 2067 mCurrentModule.hardResetSettings(mSettingsManager); 2068 2069 profile.mark(); 2070 mCurrentModule.resume(); 2071 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2072 NavigationChange.InteractionCause.BUTTON); 2073 setSwipingEnabled(true); 2074 profile.mark("mCurrentModule.resume"); 2075 2076 if (!mResetToPreviewOnResume) { 2077 FilmstripItem item = mDataAdapter.getItemAt( 2078 mFilmstripController.getCurrentAdapterIndex()); 2079 if (item != null) { 2080 mDataAdapter.refresh(item.getData().getUri()); 2081 } 2082 } 2083 2084 // The share button might be disabled to avoid double tapping. 2085 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true); 2086 // Default is showing the preview, unless disabled by explicitly 2087 // starting an activity we want to return from to the filmstrip rather 2088 // than the preview. 2089 mResetToPreviewOnResume = true; 2090 2091 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 2092 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 2093 if (!mSecureCamera) { 2094 // If it's secure camera, requestLoad() should not be called 2095 // as it will load all the data. 2096 if (!mFilmstripVisible) { 2097 mDataAdapter.requestLoad(new Callback<Void>() { 2098 @Override 2099 public void onCallback(Void result) { 2100 fillTemporarySessions(); 2101 } 2102 }); 2103 } else { 2104 mDataAdapter.requestLoadNewPhotos(); 2105 } 2106 } 2107 } 2108 mLocalImagesObserver.setActivityPaused(false); 2109 mLocalVideosObserver.setActivityPaused(false); 2110 if (!mSecureCamera) { 2111 mLocalImagesObserver.setForegroundChangeListener( 2112 new FilmstripContentObserver.ChangeListener() { 2113 @Override 2114 public void onChange() { 2115 mDataAdapter.requestLoadNewPhotos(); 2116 } 2117 }); 2118 } 2119 2120 keepScreenOnForAWhile(); 2121 2122 // Lights-out mode at all times. 2123 final View rootView = findViewById(R.id.activity_root_view); 2124 mLightsOutRunnable.run(); 2125 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( 2126 new OnSystemUiVisibilityChangeListener() { 2127 @Override 2128 public void onSystemUiVisibilityChange(int visibility) { 2129 mMainHandler.removeCallbacks(mLightsOutRunnable); 2130 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS); 2131 } 2132 }); 2133 2134 profile.mark(); 2135 mPanoramaViewHelper.onResume(); 2136 profile.mark("mPanoramaViewHelper.onResume()"); 2137 2138 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager); 2139 // Enable location recording if the setting is on. 2140 final boolean locationRecordingEnabled = 2141 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION); 2142 mLocationManager.recordLocation(locationRecordingEnabled); 2143 2144 final int previewVisibility = getPreviewVisibility(); 2145 updatePreviewRendering(previewVisibility); 2146 2147 mMotionManager.start(); 2148 profile.stop(); 2149 } 2150 fillTemporarySessions()2151 private void fillTemporarySessions() { 2152 if (mSecureCamera) { 2153 return; 2154 } 2155 // There might be sessions still in flight (processed by our service). 2156 // Make sure they're added to the filmstrip. 2157 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener); 2158 } 2159 2160 @Override onStartTasks()2161 public void onStartTasks() { 2162 mIsActivityRunning = true; 2163 mPanoramaViewHelper.onStart(); 2164 2165 /* 2166 * If we're starting after launching a different Activity (lockscreen), 2167 * we need to use the last mode used in the other Activity, and 2168 * not the old one from this Activity. 2169 * 2170 * This needs to happen before CameraAppUI.resume() in order to set the 2171 * mode cover icon to the actual last mode used. 2172 * 2173 * Right now we exclude capture intents from this logic. 2174 */ 2175 int modeIndex = getModeIndex(); 2176 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) { 2177 onModeSelected(modeIndex); 2178 } 2179 2180 if (mResetToPreviewOnResume) { 2181 mCameraAppUI.resume(); 2182 mResetToPreviewOnResume = false; 2183 } 2184 } 2185 2186 @Override onStopTasks()2187 protected void onStopTasks() { 2188 mIsActivityRunning = false; 2189 mPanoramaViewHelper.onStop(); 2190 2191 mLocationManager.disconnect(); 2192 } 2193 2194 @Override onDestroyTasks()2195 public void onDestroyTasks() { 2196 if (mSecureCamera) { 2197 unregisterReceiver(mShutdownReceiver); 2198 } 2199 2200 // Ensure anything that checks for "isPaused" returns true. 2201 mPaused = true; 2202 2203 mSettingsManager.removeAllListeners(); 2204 if (mCameraController != null) { 2205 mCameraController.removeCallbackReceiver(); 2206 mCameraController.setCameraExceptionHandler(null); 2207 } 2208 if (mLocalImagesObserver != null) { 2209 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 2210 } 2211 if (mLocalVideosObserver != null) { 2212 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 2213 } 2214 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener); 2215 if (mCameraAppUI != null) { 2216 mCameraAppUI.onDestroy(); 2217 } 2218 if (mModeListView != null) { 2219 mModeListView.setVisibilityChangedListener(null); 2220 } 2221 mCameraController = null; 2222 mSettingsManager = null; 2223 mOrientationManager = null; 2224 mButtonManager = null; 2225 if (mSoundPlayer != null) { 2226 mSoundPlayer.release(); 2227 } 2228 if (mFirstRunDialog != null) { 2229 mFirstRunDialog.dismiss(); 2230 } 2231 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1); 2232 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO); 2233 } 2234 2235 @Override onConfigurationChanged(Configuration config)2236 public void onConfigurationChanged(Configuration config) { 2237 super.onConfigurationChanged(config); 2238 Log.v(TAG, "onConfigurationChanged"); 2239 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 2240 return; 2241 } 2242 2243 if (mLastLayoutOrientation != config.orientation) { 2244 mLastLayoutOrientation = config.orientation; 2245 mCurrentModule.onLayoutOrientationChanged( 2246 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 2247 } 2248 } 2249 2250 @Override onKeyDown(int keyCode, KeyEvent event)2251 public boolean onKeyDown(int keyCode, KeyEvent event) { 2252 if (!mFilmstripVisible) { 2253 if (mCurrentModule.onKeyDown(keyCode, event)) { 2254 return true; 2255 } 2256 // Prevent software keyboard or voice search from showing up. 2257 if (keyCode == KeyEvent.KEYCODE_SEARCH 2258 || keyCode == KeyEvent.KEYCODE_MENU) { 2259 if (event.isLongPress()) { 2260 return true; 2261 } 2262 } 2263 } 2264 2265 return super.onKeyDown(keyCode, event); 2266 } 2267 2268 @Override onKeyUp(int keyCode, KeyEvent event)2269 public boolean onKeyUp(int keyCode, KeyEvent event) { 2270 if (!mFilmstripVisible) { 2271 // If a module is in the middle of capture, it should 2272 // consume the key event. 2273 if (mCurrentModule.onKeyUp(keyCode, event)) { 2274 return true; 2275 } else if (keyCode == KeyEvent.KEYCODE_MENU 2276 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 2277 // Let the mode list view consume the event. 2278 mCameraAppUI.openModeList(); 2279 return true; 2280 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 2281 mCameraAppUI.showFilmstrip(); 2282 return true; 2283 } 2284 } else { 2285 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 2286 mFilmstripController.goToNextItem(); 2287 return true; 2288 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 2289 boolean wentToPrevious = mFilmstripController.goToPreviousItem(); 2290 if (!wentToPrevious) { 2291 // at beginning of filmstrip, hide and go back to preview 2292 mCameraAppUI.hideFilmstrip(); 2293 } 2294 return true; 2295 } 2296 } 2297 return super.onKeyUp(keyCode, event); 2298 } 2299 2300 @Override onBackPressed()2301 public void onBackPressed() { 2302 if (!mCameraAppUI.onBackPressed()) { 2303 if (!mCurrentModule.onBackPressed()) { 2304 super.onBackPressed(); 2305 } 2306 } 2307 } 2308 2309 @Override isAutoRotateScreen()2310 public boolean isAutoRotateScreen() { 2311 // TODO: Move to OrientationManager. 2312 return mAutoRotateScreen; 2313 } 2314 2315 @Override onCreateOptionsMenu(Menu menu)2316 public boolean onCreateOptionsMenu(Menu menu) { 2317 MenuInflater inflater = getMenuInflater(); 2318 inflater.inflate(R.menu.filmstrip_menu, menu); 2319 mActionBarMenu = menu; 2320 2321 // add a button for launching the gallery 2322 if (mGalleryIntent != null) { 2323 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent); 2324 if (appName != null) { 2325 MenuItem menuItem = menu.add(appName); 2326 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 2327 menuItem.setIntent(mGalleryIntent); 2328 2329 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent); 2330 if (galleryLogo != null) { 2331 menuItem.setIcon(galleryLogo); 2332 } 2333 } 2334 } 2335 2336 return super.onCreateOptionsMenu(menu); 2337 } 2338 getStorageSpaceBytes()2339 protected long getStorageSpaceBytes() { 2340 synchronized (mStorageSpaceLock) { 2341 return mStorageSpaceBytes; 2342 } 2343 } 2344 2345 protected interface OnStorageUpdateDoneListener { onStorageUpdateDone(long bytes)2346 public void onStorageUpdateDone(long bytes); 2347 } 2348 updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback)2349 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) { 2350 /* 2351 * We execute disk operations on a background thread in order to 2352 * free up the UI thread. Synchronizing on the lock below ensures 2353 * that when getStorageSpaceBytes is called, the main thread waits 2354 * until this method has completed. 2355 * 2356 * However, .execute() does not ensure this execution block will be 2357 * run right away (.execute() schedules this AsyncTask for sometime 2358 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 2359 * tries to execute the task in parellel with other AsyncTasks, but 2360 * there's still no guarantee). 2361 * e.g. don't call this then immediately call getStorageSpaceBytes(). 2362 * Instead, pass in an OnStorageUpdateDoneListener. 2363 */ 2364 (new AsyncTask<Void, Void, Long>() { 2365 @Override 2366 protected Long doInBackground(Void ... arg) { 2367 synchronized (mStorageSpaceLock) { 2368 mStorageSpaceBytes = Storage.instance().getAvailableSpace(); 2369 return mStorageSpaceBytes; 2370 } 2371 } 2372 2373 @Override 2374 protected void onPostExecute(Long bytes) { 2375 updateStorageHint(bytes); 2376 // This callback returns after I/O to check disk, so we could be 2377 // pausing and shutting down. If so, don't bother invoking. 2378 if (callback != null && !mPaused) { 2379 callback.onStorageUpdateDone(bytes); 2380 } else { 2381 Log.v(TAG, "ignoring storage callback after activity pause"); 2382 } 2383 } 2384 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2385 } 2386 updateStorageHint(long storageSpace)2387 protected void updateStorageHint(long storageSpace) { 2388 if (!mIsActivityRunning) { 2389 return; 2390 } 2391 2392 String message = null; 2393 if (storageSpace == Storage.UNAVAILABLE) { 2394 message = getString(R.string.no_storage); 2395 } else if (storageSpace == Storage.PREPARING) { 2396 message = getString(R.string.preparing_sd); 2397 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 2398 message = getString(R.string.access_sd_fail); 2399 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 2400 message = getString(R.string.spaceIsLow_content); 2401 } 2402 2403 if (message != null) { 2404 Log.w(TAG, "Storage warning: " + message); 2405 if (mStorageHint == null) { 2406 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message); 2407 } else { 2408 mStorageHint.setText(message); 2409 } 2410 mStorageHint.show(); 2411 UsageStatistics.instance().storageWarning(storageSpace); 2412 2413 // Disable all user interactions, 2414 mCameraAppUI.setDisableAllUserInteractions(true); 2415 } else if (mStorageHint != null) { 2416 mStorageHint.cancel(); 2417 mStorageHint = null; 2418 2419 // Re-enable all user interactions. 2420 mCameraAppUI.setDisableAllUserInteractions(false); 2421 } 2422 } 2423 setResultEx(int resultCode)2424 protected void setResultEx(int resultCode) { 2425 mResultCodeForTesting = resultCode; 2426 setResult(resultCode); 2427 } 2428 setResultEx(int resultCode, Intent data)2429 protected void setResultEx(int resultCode, Intent data) { 2430 mResultCodeForTesting = resultCode; 2431 mResultDataForTesting = data; 2432 setResult(resultCode, data); 2433 } 2434 getResultCode()2435 public int getResultCode() { 2436 return mResultCodeForTesting; 2437 } 2438 getResultData()2439 public Intent getResultData() { 2440 return mResultDataForTesting; 2441 } 2442 isSecureCamera()2443 public boolean isSecureCamera() { 2444 return mSecureCamera; 2445 } 2446 2447 @Override isPaused()2448 public boolean isPaused() { 2449 return mPaused; 2450 } 2451 2452 @Override getPreferredChildModeIndex(int modeIndex)2453 public int getPreferredChildModeIndex(int modeIndex) { 2454 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2455 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager); 2456 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) { 2457 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 2458 } 2459 } 2460 return modeIndex; 2461 } 2462 2463 @Override onModeSelected(int modeIndex)2464 public void onModeSelected(int modeIndex) { 2465 if (mCurrentModeIndex == modeIndex) { 2466 return; 2467 } 2468 2469 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 2470 // Record last used camera mode for quick switching 2471 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 2472 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2473 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2474 Keys.KEY_CAMERA_MODULE_LAST_USED, 2475 modeIndex); 2476 } 2477 2478 closeModule(mCurrentModule); 2479 2480 // Select the correct module index from the mode switcher index. 2481 modeIndex = getPreferredChildModeIndex(modeIndex); 2482 setModuleFromModeIndex(modeIndex); 2483 2484 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 2485 mCameraAppUI.addShutterListener(mCurrentModule); 2486 openModule(mCurrentModule); 2487 // Store the module index so we can use it the next time the Camera 2488 // starts up. 2489 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2490 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex); 2491 } 2492 2493 /** 2494 * Shows the settings dialog. 2495 */ 2496 @Override onSettingsSelected()2497 public void onSettingsSelected() { 2498 UsageStatistics.instance().controlUsed( 2499 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 2500 Intent intent = new Intent(this, CameraSettingsActivity.class); 2501 if (!isKeyguardLocked()) { 2502 startActivity(intent); 2503 } else { 2504 /* Need to explicitly request keyguard dismissal for PIN/pattern 2505 * entry to show up directly. */ 2506 requestDismissKeyguard( 2507 /* requesting Activity: */ CameraActivity.this, 2508 new KeyguardDismissCallback() { 2509 @Override 2510 public void onDismissSucceeded() { 2511 /* Need to use launchActivityByIntent() so that going 2512 * back from settings after unlock leads to main 2513 * activity instead of dismissing camera entirely. */ 2514 launchActivityByIntent(intent); 2515 } 2516 @Override 2517 public void onDismissError() { 2518 Log.e(TAG, "Keyguard dismissal failed."); 2519 } 2520 @Override 2521 public void onDismissCancelled() { 2522 Log.d(TAG, "Keyguard dismissal canceled."); 2523 } 2524 } 2525 ); 2526 } 2527 } 2528 2529 @Override freezeScreenUntilPreviewReady()2530 public void freezeScreenUntilPreviewReady() { 2531 mCameraAppUI.freezeScreenUntilPreviewReady(); 2532 } 2533 2534 @Override getModuleId(int modeIndex)2535 public int getModuleId(int modeIndex) { 2536 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2537 if (agent == null) { 2538 return -1; 2539 } 2540 return agent.getModuleId(); 2541 } 2542 2543 /** 2544 * Sets the mCurrentModuleIndex, creates a new module instance for the given 2545 * index an sets it as mCurrentModule. 2546 */ setModuleFromModeIndex(int modeIndex)2547 private void setModuleFromModeIndex(int modeIndex) { 2548 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2549 if (agent == null) { 2550 return; 2551 } 2552 if (!agent.requestAppForCamera()) { 2553 mCameraController.closeCamera(true); 2554 } 2555 mCurrentModeIndex = agent.getModuleId(); 2556 mCurrentModule = (CameraModule) agent.createModule(this, getIntent()); 2557 } 2558 2559 @Override getSettingsManager()2560 public SettingsManager getSettingsManager() { 2561 return mSettingsManager; 2562 } 2563 2564 @Override getResolutionSetting()2565 public ResolutionSetting getResolutionSetting() { 2566 return mResolutionSetting; 2567 } 2568 2569 @Override getServices()2570 public CameraServices getServices() { 2571 return CameraServicesImpl.instance(); 2572 } 2573 2574 @Override getFatalErrorHandler()2575 public FatalErrorHandler getFatalErrorHandler() { 2576 return mFatalErrorHandler; 2577 } 2578 getSupportedModeNames()2579 public List<String> getSupportedModeNames() { 2580 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 2581 List<String> supported = new ArrayList<String>(); 2582 2583 for (Integer modeIndex : indices) { 2584 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 2585 if (name != null && !name.equals("")) { 2586 supported.add(name); 2587 } 2588 } 2589 return supported; 2590 } 2591 2592 @Override getButtonManager()2593 public ButtonManager getButtonManager() { 2594 if (mButtonManager == null) { 2595 mButtonManager = new ButtonManager(this); 2596 } 2597 return mButtonManager; 2598 } 2599 2600 @Override getSoundPlayer()2601 public SoundPlayer getSoundPlayer() { 2602 return mSoundPlayer; 2603 } 2604 2605 /** 2606 * Launches an ACTION_EDIT intent for the given local data item. If 2607 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2608 * the user start either the tiny planet editor or another photo editor. 2609 * 2610 * @param data The data item to edit. 2611 */ launchEditor(FilmstripItem data)2612 public void launchEditor(FilmstripItem data) { 2613 Intent intent = new Intent(Intent.ACTION_EDIT) 2614 .setDataAndType(data.getData().getUri(), data.getData().getMimeType()) 2615 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2616 try { 2617 launchActivityByIntent(intent); 2618 } catch (ActivityNotFoundException e) { 2619 final String msgEditWith = getResources().getString(R.string.edit_with); 2620 launchActivityByIntent(Intent.createChooser(intent, msgEditWith)); 2621 } 2622 } 2623 2624 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)2625 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2626 super.onCreateContextMenu(menu, v, menuInfo); 2627 2628 MenuInflater inflater = getMenuInflater(); 2629 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2630 } 2631 2632 @Override onContextItemSelected(MenuItem item)2633 public boolean onContextItemSelected(MenuItem item) { 2634 switch (item.getItemId()) { 2635 case R.id.tiny_planet_editor: 2636 mMyFilmstripBottomControlListener.onTinyPlanet(); 2637 return true; 2638 case R.id.photo_editor: 2639 mMyFilmstripBottomControlListener.onEdit(); 2640 return true; 2641 } 2642 return false; 2643 } 2644 2645 /** 2646 * Launch the tiny planet editor. 2647 * 2648 * @param data The data must be a 360 degree stereographically mapped 2649 * panoramic image. It will not be modified, instead a new item 2650 * with the result will be added to the filmstrip. 2651 */ launchTinyPlanetEditor(FilmstripItem data)2652 public void launchTinyPlanetEditor(FilmstripItem data) { 2653 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2654 Bundle bundle = new Bundle(); 2655 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString()); 2656 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle()); 2657 fragment.setArguments(bundle); 2658 fragment.show(getFragmentManager(), "tiny_planet"); 2659 } 2660 2661 /** 2662 * Returns what UI mode (capture mode or filmstrip) we are in. 2663 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2664 */ currentUserInterfaceMode()2665 private int currentUserInterfaceMode() { 2666 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2667 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2668 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2669 } 2670 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2671 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2672 } 2673 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2674 mode = NavigationChange.Mode.LENS_BLUR; 2675 } 2676 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2677 mode = NavigationChange.Mode.HDR_PLUS; 2678 } 2679 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2680 mode = NavigationChange.Mode.PHOTO_SPHERE; 2681 } 2682 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2683 mode = NavigationChange.Mode.PANORAMA; 2684 } 2685 if (mFilmstripVisible) { 2686 mode = NavigationChange.Mode.FILMSTRIP; 2687 } 2688 return mode; 2689 } 2690 openModule(CameraModule module)2691 private void openModule(CameraModule module) { 2692 module.init(this, isSecureCamera(), isCaptureIntent()); 2693 module.hardResetSettings(mSettingsManager); 2694 // Hide accessibility zoom UI by default. Modules will enable it themselves if required. 2695 getCameraAppUI().hideAccessibilityZoomUI(); 2696 if (!mPaused) { 2697 module.resume(); 2698 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2699 NavigationChange.InteractionCause.BUTTON); 2700 updatePreviewVisibility(); 2701 } 2702 } 2703 closeModule(CameraModule module)2704 private void closeModule(CameraModule module) { 2705 module.pause(); 2706 mCameraAppUI.clearModuleUI(); 2707 } 2708 performDeletion()2709 private void performDeletion() { 2710 if (!mPendingDeletion) { 2711 return; 2712 } 2713 hideUndoDeletionBar(false); 2714 mDataAdapter.executeDeletion(); 2715 } 2716 showUndoDeletionBar()2717 public void showUndoDeletionBar() { 2718 if (mPendingDeletion) { 2719 performDeletion(); 2720 } 2721 Log.v(TAG, "showing undo bar"); 2722 mPendingDeletion = true; 2723 if (mUndoDeletionBar == null) { 2724 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2725 mAboveFilmstripControlLayout, true); 2726 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2727 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2728 button.setOnClickListener(new View.OnClickListener() { 2729 @Override 2730 public void onClick(View view) { 2731 mDataAdapter.undoDeletion(); 2732 // Fix for b/21666018: When undoing a delete in Fullscreen 2733 // mode, just flip 2734 // back to the filmstrip to force a refresh. 2735 if (mFilmstripController.inFullScreen()) { 2736 mFilmstripController.goToFilmstrip(); 2737 } 2738 hideUndoDeletionBar(true); 2739 } 2740 }); 2741 // Setting undo bar clickable to avoid touch events going through 2742 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2743 mUndoDeletionBar.setClickable(true); 2744 // When there is user interaction going on with the undo button, we 2745 // do not want to hide the undo bar. 2746 button.setOnTouchListener(new View.OnTouchListener() { 2747 @Override 2748 public boolean onTouch(View v, MotionEvent event) { 2749 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2750 mIsUndoingDeletion = true; 2751 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2752 mIsUndoingDeletion = false; 2753 } 2754 return false; 2755 } 2756 }); 2757 } 2758 mUndoDeletionBar.setAlpha(0f); 2759 mUndoDeletionBar.setVisibility(View.VISIBLE); 2760 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2761 } 2762 hideUndoDeletionBar(boolean withAnimation)2763 private void hideUndoDeletionBar(boolean withAnimation) { 2764 Log.v(TAG, "Hiding undo deletion bar"); 2765 mPendingDeletion = false; 2766 if (mUndoDeletionBar != null) { 2767 if (withAnimation) { 2768 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2769 .setListener(new Animator.AnimatorListener() { 2770 @Override 2771 public void onAnimationStart(Animator animation) { 2772 // Do nothing. 2773 } 2774 2775 @Override 2776 public void onAnimationEnd(Animator animation) { 2777 mUndoDeletionBar.setVisibility(View.GONE); 2778 } 2779 2780 @Override 2781 public void onAnimationCancel(Animator animation) { 2782 // Do nothing. 2783 } 2784 2785 @Override 2786 public void onAnimationRepeat(Animator animation) { 2787 // Do nothing. 2788 } 2789 }).start(); 2790 } else { 2791 mUndoDeletionBar.setVisibility(View.GONE); 2792 } 2793 } 2794 } 2795 2796 /** 2797 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2798 * capture intent. 2799 * 2800 * @param enable {@code true} to enable swipe. 2801 */ setSwipingEnabled(boolean enable)2802 public void setSwipingEnabled(boolean enable) { 2803 // TODO: Bring back the functionality. 2804 if (isCaptureIntent()) { 2805 // lockPreview(true); 2806 } else { 2807 // lockPreview(!enable); 2808 } 2809 } 2810 2811 // Accessor methods for getting latency times used in performance testing getFirstPreviewTime()2812 public long getFirstPreviewTime() { 2813 if (mCurrentModule instanceof PhotoModule) { 2814 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2815 if (coverHiddenTime != -1) { 2816 return coverHiddenTime - mOnCreateTime; 2817 } 2818 } 2819 return -1; 2820 } 2821 getAutoFocusTime()2822 public long getAutoFocusTime() { 2823 return (mCurrentModule instanceof PhotoModule) ? 2824 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2825 } 2826 getShutterLag()2827 public long getShutterLag() { 2828 return (mCurrentModule instanceof PhotoModule) ? 2829 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2830 } 2831 getShutterToPictureDisplayedTime()2832 public long getShutterToPictureDisplayedTime() { 2833 return (mCurrentModule instanceof PhotoModule) ? 2834 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2835 } 2836 getPictureDisplayedToJpegCallbackTime()2837 public long getPictureDisplayedToJpegCallbackTime() { 2838 return (mCurrentModule instanceof PhotoModule) ? 2839 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2840 } 2841 getJpegCallbackFinishTime()2842 public long getJpegCallbackFinishTime() { 2843 return (mCurrentModule instanceof PhotoModule) ? 2844 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2845 } 2846 getCaptureStartTime()2847 public long getCaptureStartTime() { 2848 return (mCurrentModule instanceof PhotoModule) ? 2849 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2850 } 2851 isRecording()2852 public boolean isRecording() { 2853 return (mCurrentModule instanceof VideoModule) ? 2854 ((VideoModule) mCurrentModule).isRecording() : false; 2855 } 2856 getCameraOpenErrorCallback()2857 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() { 2858 return mCameraController; 2859 } 2860 2861 // For debugging purposes only. getCurrentModule()2862 public CameraModule getCurrentModule() { 2863 return mCurrentModule; 2864 } 2865 2866 @Override showTutorial(AbstractTutorialOverlay tutorial)2867 public void showTutorial(AbstractTutorialOverlay tutorial) { 2868 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2869 } 2870 2871 @Override finishActivityWithIntentCompleted(Intent resultIntent)2872 public void finishActivityWithIntentCompleted(Intent resultIntent) { 2873 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent); 2874 } 2875 2876 @Override finishActivityWithIntentCanceled()2877 public void finishActivityWithIntentCanceled() { 2878 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent()); 2879 } 2880 finishActivityWithIntentResult(int resultCode, Intent resultIntent)2881 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) { 2882 mResultCodeForTesting = resultCode; 2883 mResultDataForTesting = resultIntent; 2884 setResult(resultCode, resultIntent); 2885 finish(); 2886 } 2887 keepScreenOnForAWhile()2888 private void keepScreenOnForAWhile() { 2889 if (mKeepScreenOn) { 2890 return; 2891 } 2892 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2893 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2894 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2895 } 2896 resetScreenOn()2897 private void resetScreenOn() { 2898 mKeepScreenOn = false; 2899 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2900 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2901 } 2902 2903 /** 2904 * @return {@code true} if the Gallery is launched successfully. 2905 */ startGallery()2906 private boolean startGallery() { 2907 if (mGalleryIntent == null) { 2908 return false; 2909 } 2910 try { 2911 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2912 NavigationChange.InteractionCause.BUTTON); 2913 Intent startGalleryIntent = new Intent(mGalleryIntent); 2914 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 2915 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex); 2916 if (currentFilmstripItem != null) { 2917 GalleryHelper.setContentUri(startGalleryIntent, 2918 currentFilmstripItem.getData().getUri()); 2919 } 2920 launchActivityByIntent(startGalleryIntent); 2921 } catch (ActivityNotFoundException e) { 2922 Log.w(TAG, "Failed to launch gallery activity, closing"); 2923 } 2924 return false; 2925 } 2926 setNfcBeamPushUriFromData(FilmstripItem data)2927 private void setNfcBeamPushUriFromData(FilmstripItem data) { 2928 final Uri uri = data.getData().getUri(); 2929 if (uri != Uri.EMPTY) { 2930 mNfcPushUris[0] = uri; 2931 } else { 2932 mNfcPushUris[0] = null; 2933 } 2934 } 2935 2936 /** 2937 * Updates the visibility of the filmstrip bottom controls and action bar. 2938 */ updateUiByData(final int index)2939 private void updateUiByData(final int index) { 2940 final FilmstripItem currentData = mDataAdapter.getItemAt(index); 2941 if (currentData == null) { 2942 Log.w(TAG, "Current data ID not found."); 2943 hideSessionProgress(); 2944 return; 2945 } 2946 updateActionBarMenu(currentData); 2947 2948 /* Bottom controls. */ 2949 updateBottomControlsByData(currentData); 2950 2951 if (isSecureCamera()) { 2952 // We cannot show buttons in secure camera since go to other 2953 // activities might create a security hole. 2954 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2955 return; 2956 } 2957 2958 setNfcBeamPushUriFromData(currentData); 2959 2960 if (!mDataAdapter.isMetadataUpdatedAt(index)) { 2961 mDataAdapter.updateMetadataAt(index); 2962 } 2963 } 2964 2965 /** 2966 * Updates the bottom controls based on the data. 2967 */ updateBottomControlsByData(final FilmstripItem currentData)2968 private void updateBottomControlsByData(final FilmstripItem currentData) { 2969 2970 final CameraAppUI.BottomPanel filmstripBottomPanel = 2971 mCameraAppUI.getFilmstripBottomControls(); 2972 filmstripBottomPanel.showControls(); 2973 filmstripBottomPanel.setEditButtonVisibility( 2974 currentData.getAttributes().canEdit()); 2975 filmstripBottomPanel.setShareButtonVisibility( 2976 currentData.getAttributes().canShare()); 2977 filmstripBottomPanel.setDeleteButtonVisibility( 2978 currentData.getAttributes().canDelete()); 2979 2980 /* Progress bar */ 2981 2982 Uri contentUri = currentData.getData().getUri(); 2983 CaptureSessionManager sessionManager = getServices() 2984 .getCaptureSessionManager(); 2985 2986 if (sessionManager.hasErrorMessage(contentUri)) { 2987 showProcessError(sessionManager.getErrorMessageId(contentUri)); 2988 } else { 2989 filmstripBottomPanel.hideProgressError(); 2990 hideSessionProgress(); 2991 } 2992 2993 /* View button */ 2994 2995 // We need to add this to a separate DB. 2996 final int viewButtonVisibility; 2997 if (currentData.getMetadata().isUsePanoramaViewer()) { 2998 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2999 } else if (currentData.getMetadata().isHasRgbzData()) { 3000 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 3001 } else { 3002 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 3003 } 3004 3005 filmstripBottomPanel.setTinyPlanetEnabled( 3006 currentData.getMetadata().isPanorama360()); 3007 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 3008 } 3009 showDetailsDialog(int index)3010 private void showDetailsDialog(int index) { 3011 final FilmstripItem data = mDataAdapter.getItemAt(index); 3012 if (data == null) { 3013 return; 3014 } 3015 Optional<MediaDetails> details = data.getMediaDetails(); 3016 if (!details.isPresent()) { 3017 return; 3018 } 3019 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get()); 3020 detailDialog.show(); 3021 UsageStatistics.instance().mediaInteraction( 3022 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS, 3023 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index)); 3024 } 3025 3026 /** 3027 * Show or hide action bar items depending on current data type. 3028 */ updateActionBarMenu(FilmstripItem data)3029 private void updateActionBarMenu(FilmstripItem data) { 3030 if (mActionBarMenu == null) { 3031 return; 3032 } 3033 3034 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 3035 if (detailsMenuItem == null) { 3036 return; 3037 } 3038 3039 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo(); 3040 detailsMenuItem.setVisible(showDetails); 3041 } 3042 } 3043