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