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