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