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