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