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