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