CameraActivity.java revision d2d8b711c14ee47f6f4ea16cb9fe27128462ad5b
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 2072 // Disable all user interactions, 2073 mCameraAppUI.setDisableAllUserInteractions(true); 2074 } else if (mStorageHint != null) { 2075 mStorageHint.cancel(); 2076 mStorageHint = null; 2077 2078 // Re-enable all user interactions. 2079 mCameraAppUI.setDisableAllUserInteractions(false); 2080 } 2081 } 2082 2083 protected void setResultEx(int resultCode) { 2084 mResultCodeForTesting = resultCode; 2085 setResult(resultCode); 2086 } 2087 2088 protected void setResultEx(int resultCode, Intent data) { 2089 mResultCodeForTesting = resultCode; 2090 mResultDataForTesting = data; 2091 setResult(resultCode, data); 2092 } 2093 2094 public int getResultCode() { 2095 return mResultCodeForTesting; 2096 } 2097 2098 public Intent getResultData() { 2099 return mResultDataForTesting; 2100 } 2101 2102 public boolean isSecureCamera() { 2103 return mSecureCamera; 2104 } 2105 2106 @Override 2107 public boolean isPaused() { 2108 return mPaused; 2109 } 2110 2111 @Override 2112 public int getPreferredChildModeIndex(int modeIndex) { 2113 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2114 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager); 2115 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) { 2116 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 2117 } 2118 } 2119 return modeIndex; 2120 } 2121 2122 @Override 2123 public void onModeSelected(int modeIndex) { 2124 if (mCurrentModeIndex == modeIndex) { 2125 return; 2126 } 2127 2128 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 2129 // Record last used camera mode for quick switching 2130 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 2131 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2132 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2133 Keys.KEY_CAMERA_MODULE_LAST_USED, 2134 modeIndex); 2135 } 2136 2137 closeModule(mCurrentModule); 2138 2139 // Select the correct module index from the mode switcher index. 2140 modeIndex = getPreferredChildModeIndex(modeIndex); 2141 setModuleFromModeIndex(modeIndex); 2142 2143 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 2144 mCameraAppUI.addShutterListener(mCurrentModule); 2145 mCameraAppUI.hideLetterboxing(); 2146 openModule(mCurrentModule); 2147 mCurrentModule.onOrientationChanged(mLastRawOrientation); 2148 // Store the module index so we can use it the next time the Camera 2149 // starts up. 2150 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2151 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex); 2152 } 2153 2154 /** 2155 * Shows the settings dialog. 2156 */ 2157 @Override 2158 public void onSettingsSelected() { 2159 UsageStatistics.instance().controlUsed( 2160 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 2161 Intent intent = new Intent(this, CameraSettingsActivity.class); 2162 startActivity(intent); 2163 } 2164 2165 @Override 2166 public void freezeScreenUntilPreviewReady() { 2167 mCameraAppUI.freezeScreenUntilPreviewReady(); 2168 } 2169 2170 /** 2171 * Sets the mCurrentModuleIndex, creates a new module instance for the given 2172 * index an sets it as mCurrentModule. 2173 */ 2174 private void setModuleFromModeIndex(int modeIndex) { 2175 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2176 if (agent == null) { 2177 return; 2178 } 2179 if (!agent.requestAppForCamera()) { 2180 mCameraController.closeCamera(true); 2181 } 2182 mCurrentModeIndex = agent.getModuleId(); 2183 mCurrentModule = (CameraModule) agent.createModule(this); 2184 } 2185 2186 @Override 2187 public SettingsManager getSettingsManager() { 2188 return mSettingsManager; 2189 } 2190 2191 @Override 2192 public CameraServices getServices() { 2193 return (CameraServices) getApplication(); 2194 } 2195 2196 public List<String> getSupportedModeNames() { 2197 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 2198 List<String> supported = new ArrayList<String>(); 2199 2200 for (Integer modeIndex : indices) { 2201 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 2202 if (name != null && !name.equals("")) { 2203 supported.add(name); 2204 } 2205 } 2206 return supported; 2207 } 2208 2209 @Override 2210 public ButtonManager getButtonManager() { 2211 if (mButtonManager == null) { 2212 mButtonManager = new ButtonManager(this); 2213 } 2214 return mButtonManager; 2215 } 2216 2217 @Override 2218 public SoundPlayer getSoundPlayer() { 2219 return mSoundPlayer; 2220 } 2221 2222 /** 2223 * Creates an AlertDialog appropriate for choosing whether to enable 2224 * location on the first run of the app. 2225 */ 2226 public AlertDialog getFirstTimeLocationAlert() { 2227 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2228 builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() { 2229 @Override 2230 public void onCallback(Boolean locationOn) { 2231 Keys.setLocation(mSettingsManager, locationOn, mLocationManager); 2232 } 2233 }); 2234 if (builder != null) { 2235 return builder.create(); 2236 } else { 2237 return null; 2238 } 2239 } 2240 2241 /** 2242 * Launches an ACTION_EDIT intent for the given local data item. If 2243 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2244 * the user start either the tiny planet editor or another photo edior. 2245 * 2246 * @param data The data item to edit. 2247 */ 2248 public void launchEditor(LocalData data) { 2249 Intent intent = new Intent(Intent.ACTION_EDIT) 2250 .setDataAndType(data.getUri(), data.getMimeType()) 2251 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2252 try { 2253 launchActivityByIntent(intent); 2254 } catch (ActivityNotFoundException e) { 2255 final String msgEditWith = getResources().getString(R.string.edit_with); 2256 launchActivityByIntent(Intent.createChooser(intent, msgEditWith)); 2257 } 2258 } 2259 2260 @Override 2261 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2262 super.onCreateContextMenu(menu, v, menuInfo); 2263 2264 MenuInflater inflater = getMenuInflater(); 2265 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2266 } 2267 2268 @Override 2269 public boolean onContextItemSelected(MenuItem item) { 2270 switch (item.getItemId()) { 2271 case R.id.tiny_planet_editor: 2272 mMyFilmstripBottomControlListener.onTinyPlanet(); 2273 return true; 2274 case R.id.photo_editor: 2275 mMyFilmstripBottomControlListener.onEdit(); 2276 return true; 2277 } 2278 return false; 2279 } 2280 2281 /** 2282 * Launch the tiny planet editor. 2283 * 2284 * @param data The data must be a 360 degree stereographically mapped 2285 * panoramic image. It will not be modified, instead a new item 2286 * with the result will be added to the filmstrip. 2287 */ 2288 public void launchTinyPlanetEditor(LocalData data) { 2289 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2290 Bundle bundle = new Bundle(); 2291 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString()); 2292 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 2293 fragment.setArguments(bundle); 2294 fragment.show(getFragmentManager(), "tiny_planet"); 2295 } 2296 2297 /** 2298 * Returns what UI mode (capture mode or filmstrip) we are in. 2299 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2300 */ 2301 private int currentUserInterfaceMode() { 2302 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2303 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2304 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2305 } 2306 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2307 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2308 } 2309 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2310 mode = NavigationChange.Mode.LENS_BLUR; 2311 } 2312 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2313 mode = NavigationChange.Mode.HDR_PLUS; 2314 } 2315 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2316 mode = NavigationChange.Mode.PHOTO_SPHERE; 2317 } 2318 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2319 mode = NavigationChange.Mode.PANORAMA; 2320 } 2321 if (mFilmstripVisible) { 2322 mode = NavigationChange.Mode.FILMSTRIP; 2323 } 2324 return mode; 2325 } 2326 2327 private void openModule(CameraModule module) { 2328 module.init(this, isSecureCamera(), isCaptureIntent()); 2329 module.hardResetSettings(mSettingsManager); 2330 if (!mPaused) { 2331 module.resume(); 2332 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2333 NavigationChange.InteractionCause.BUTTON); 2334 updatePreviewVisibility(); 2335 } 2336 } 2337 2338 private void closeModule(CameraModule module) { 2339 module.pause(); 2340 mCameraAppUI.clearModuleUI(); 2341 } 2342 2343 private void performDeletion() { 2344 if (!mPendingDeletion) { 2345 return; 2346 } 2347 hideUndoDeletionBar(false); 2348 mDataAdapter.executeDeletion(); 2349 } 2350 2351 public void showUndoDeletionBar() { 2352 if (mPendingDeletion) { 2353 performDeletion(); 2354 } 2355 Log.v(TAG, "showing undo bar"); 2356 mPendingDeletion = true; 2357 if (mUndoDeletionBar == null) { 2358 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2359 mAboveFilmstripControlLayout, true); 2360 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2361 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2362 button.setOnClickListener(new View.OnClickListener() { 2363 @Override 2364 public void onClick(View view) { 2365 mDataAdapter.undoDataRemoval(); 2366 hideUndoDeletionBar(true); 2367 } 2368 }); 2369 // Setting undo bar clickable to avoid touch events going through 2370 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2371 mUndoDeletionBar.setClickable(true); 2372 // When there is user interaction going on with the undo button, we 2373 // do not want to hide the undo bar. 2374 button.setOnTouchListener(new View.OnTouchListener() { 2375 @Override 2376 public boolean onTouch(View v, MotionEvent event) { 2377 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2378 mIsUndoingDeletion = true; 2379 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2380 mIsUndoingDeletion = false; 2381 } 2382 return false; 2383 } 2384 }); 2385 } 2386 mUndoDeletionBar.setAlpha(0f); 2387 mUndoDeletionBar.setVisibility(View.VISIBLE); 2388 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2389 } 2390 2391 private void hideUndoDeletionBar(boolean withAnimation) { 2392 Log.v(TAG, "Hiding undo deletion bar"); 2393 mPendingDeletion = false; 2394 if (mUndoDeletionBar != null) { 2395 if (withAnimation) { 2396 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2397 .setListener(new Animator.AnimatorListener() { 2398 @Override 2399 public void onAnimationStart(Animator animation) { 2400 // Do nothing. 2401 } 2402 2403 @Override 2404 public void onAnimationEnd(Animator animation) { 2405 mUndoDeletionBar.setVisibility(View.GONE); 2406 } 2407 2408 @Override 2409 public void onAnimationCancel(Animator animation) { 2410 // Do nothing. 2411 } 2412 2413 @Override 2414 public void onAnimationRepeat(Animator animation) { 2415 // Do nothing. 2416 } 2417 }).start(); 2418 } else { 2419 mUndoDeletionBar.setVisibility(View.GONE); 2420 } 2421 } 2422 } 2423 2424 @Override 2425 public void onOrientationChanged(int orientation) { 2426 // We keep the last known orientation. So if the user first orient 2427 // the camera then point the camera to floor or sky, we still have 2428 // the correct orientation. 2429 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) { 2430 return; 2431 } 2432 mLastRawOrientation = orientation; 2433 if (mCurrentModule != null) { 2434 mCurrentModule.onOrientationChanged(orientation); 2435 } 2436 } 2437 2438 /** 2439 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2440 * capture intent. 2441 * 2442 * @param enable {@code true} to enable swipe. 2443 */ 2444 public void setSwipingEnabled(boolean enable) { 2445 // TODO: Bring back the functionality. 2446 if (isCaptureIntent()) { 2447 // lockPreview(true); 2448 } else { 2449 // lockPreview(!enable); 2450 } 2451 } 2452 2453 // Accessor methods for getting latency times used in performance testing 2454 public long getFirstPreviewTime() { 2455 if (mCurrentModule instanceof PhotoModule) { 2456 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2457 if (coverHiddenTime != -1) { 2458 return coverHiddenTime - mOnCreateTime; 2459 } 2460 } 2461 return -1; 2462 } 2463 2464 public long getAutoFocusTime() { 2465 return (mCurrentModule instanceof PhotoModule) ? 2466 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2467 } 2468 2469 public long getShutterLag() { 2470 return (mCurrentModule instanceof PhotoModule) ? 2471 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2472 } 2473 2474 public long getShutterToPictureDisplayedTime() { 2475 return (mCurrentModule instanceof PhotoModule) ? 2476 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2477 } 2478 2479 public long getPictureDisplayedToJpegCallbackTime() { 2480 return (mCurrentModule instanceof PhotoModule) ? 2481 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2482 } 2483 2484 public long getJpegCallbackFinishTime() { 2485 return (mCurrentModule instanceof PhotoModule) ? 2486 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2487 } 2488 2489 public long getCaptureStartTime() { 2490 return (mCurrentModule instanceof PhotoModule) ? 2491 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2492 } 2493 2494 public boolean isRecording() { 2495 return (mCurrentModule instanceof VideoModule) ? 2496 ((VideoModule) mCurrentModule).isRecording() : false; 2497 } 2498 2499 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() { 2500 return mCameraController; 2501 } 2502 2503 // For debugging purposes only. 2504 public CameraModule getCurrentModule() { 2505 return mCurrentModule; 2506 } 2507 2508 @Override 2509 public void showTutorial(AbstractTutorialOverlay tutorial) { 2510 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2511 } 2512 2513 @Override 2514 public void showErrorAndFinish(int messageId) { 2515 CameraUtil.showErrorAndFinish(this, messageId); 2516 } 2517 2518 /** 2519 * Reads the current location recording settings and passes it on to the 2520 * location manager. 2521 */ 2522 public void syncLocationManagerSetting() { 2523 Keys.syncLocationManager(mSettingsManager, mLocationManager); 2524 } 2525 2526 private void keepScreenOnForAWhile() { 2527 if (mKeepScreenOn) { 2528 return; 2529 } 2530 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2531 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2532 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2533 } 2534 2535 private void resetScreenOn() { 2536 mKeepScreenOn = false; 2537 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2538 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2539 } 2540 2541 /** 2542 * @return {@code true} if the Gallery is launched successfully. 2543 */ 2544 private boolean startGallery() { 2545 if (mGalleryIntent == null) { 2546 return false; 2547 } 2548 try { 2549 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2550 NavigationChange.InteractionCause.BUTTON); 2551 Intent startGalleryIntent = new Intent(mGalleryIntent); 2552 int currentDataId = mFilmstripController.getCurrentId(); 2553 LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId); 2554 if (currentLocalData != null) { 2555 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri()); 2556 } 2557 launchActivityByIntent(startGalleryIntent); 2558 } catch (ActivityNotFoundException e) { 2559 Log.w(TAG, "Failed to launch gallery activity, closing"); 2560 } 2561 return false; 2562 } 2563 2564 private void setNfcBeamPushUriFromData(LocalData data) { 2565 final Uri uri = data.getUri(); 2566 if (uri != Uri.EMPTY) { 2567 mNfcPushUris[0] = uri; 2568 } else { 2569 mNfcPushUris[0] = null; 2570 } 2571 } 2572 2573 /** 2574 * Updates the visibility of the filmstrip bottom controls and action bar. 2575 */ 2576 private void updateUiByData(final int dataId) { 2577 final LocalData currentData = mDataAdapter.getLocalData(dataId); 2578 if (currentData == null) { 2579 Log.w(TAG, "Current data ID not found."); 2580 hideSessionProgress(); 2581 return; 2582 } 2583 updateActionBarMenu(currentData); 2584 2585 /* Bottom controls. */ 2586 updateBottomControlsByData(currentData); 2587 2588 if (isSecureCamera()) { 2589 // We cannot show buttons in secure camera since go to other 2590 // activities might create a security hole. 2591 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2592 return; 2593 } 2594 2595 2596 setNfcBeamPushUriFromData(currentData); 2597 2598 if (!mDataAdapter.isMetadataUpdated(dataId)) { 2599 mDataAdapter.updateMetadata(dataId); 2600 } 2601 } 2602 2603 /** 2604 * Updates the bottom controls based on the data. 2605 */ 2606 private void updateBottomControlsByData(final LocalData currentData) { 2607 2608 final CameraAppUI.BottomPanel filmstripBottomPanel = 2609 mCameraAppUI.getFilmstripBottomControls(); 2610 filmstripBottomPanel.showControls(); 2611 filmstripBottomPanel.setEditButtonVisibility( 2612 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT)); 2613 filmstripBottomPanel.setShareButtonVisibility( 2614 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE)); 2615 filmstripBottomPanel.setDeleteButtonVisibility( 2616 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE)); 2617 2618 /* Progress bar */ 2619 2620 Uri contentUri = currentData.getUri(); 2621 CaptureSessionManager sessionManager = getServices() 2622 .getCaptureSessionManager(); 2623 2624 if (sessionManager.hasErrorMessage(contentUri)) { 2625 showProcessError(sessionManager.getErrorMesage(contentUri)); 2626 } else { 2627 filmstripBottomPanel.hideProgressError(); 2628 CaptureSession session = sessionManager.getSession(contentUri); 2629 2630 if (session != null) { 2631 int sessionProgress = session.getProgress(); 2632 2633 if (sessionProgress < 0) { 2634 hideSessionProgress(); 2635 } else { 2636 CharSequence progressMessage = session.getProgressMessage(); 2637 showSessionProgress(progressMessage); 2638 updateSessionProgress(sessionProgress); 2639 } 2640 } else { 2641 hideSessionProgress(); 2642 } 2643 } 2644 2645 /* View button */ 2646 2647 // We need to add this to a separate DB. 2648 final int viewButtonVisibility; 2649 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) { 2650 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2651 } else if (RgbzMetadataLoader.hasRGBZData(currentData)) { 2652 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 2653 } else { 2654 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 2655 } 2656 2657 filmstripBottomPanel.setTinyPlanetEnabled( 2658 PanoramaMetadataLoader.isPanorama360(currentData)); 2659 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 2660 } 2661 2662 private class PeekAnimationHandler extends Handler { 2663 private class DataAndCallback { 2664 LocalData mData; 2665 com.android.camera.util.Callback<Bitmap> mCallback; 2666 2667 public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap> 2668 callback) { 2669 mData = data; 2670 mCallback = callback; 2671 } 2672 } 2673 2674 public PeekAnimationHandler(Looper looper) { 2675 super(looper); 2676 } 2677 2678 /** 2679 * Starts the animation decoding job and posts a {@code Runnable} back 2680 * when when the decoding is done. 2681 * 2682 * @param data The data item to decode the thumbnail for. 2683 * @param callback {@link com.android.camera.util.Callback} after the 2684 * decoding is done. 2685 */ 2686 public void startDecodingJob(final LocalData data, 2687 final com.android.camera.util.Callback<Bitmap> callback) { 2688 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/, 2689 new DataAndCallback(data, callback)).sendToTarget(); 2690 } 2691 2692 @Override 2693 public void handleMessage(Message msg) { 2694 final LocalData data = ((DataAndCallback) msg.obj).mData; 2695 final com.android.camera.util.Callback<Bitmap> callback = 2696 ((DataAndCallback) msg.obj).mCallback; 2697 if (data == null || callback == null) { 2698 return; 2699 } 2700 2701 final Bitmap bitmap; 2702 switch (data.getLocalDataType()) { 2703 case LocalData.LOCAL_IN_PROGRESS_DATA: 2704 byte[] jpegData = Storage.getJpegForSession(data.getUri()); 2705 if (jpegData != null) { 2706 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 2707 } else { 2708 bitmap = null; 2709 } 2710 break; 2711 2712 case LocalData.LOCAL_IMAGE: 2713 FileInputStream stream; 2714 try { 2715 stream = new FileInputStream(data.getPath()); 2716 } catch (FileNotFoundException e) { 2717 Log.e(TAG, "File not found:" + data.getPath()); 2718 return; 2719 } 2720 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(), 2721 data.getRotation(), mAboveFilmstripControlLayout.getWidth(), 2722 mAboveFilmstripControlLayout.getMeasuredHeight()); 2723 if (data.getRotation() % 180 != 0) { 2724 int dummy = dim.x; 2725 dim.x = dim.y; 2726 dim.y = dummy; 2727 } 2728 bitmap = LocalDataUtil 2729 .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(), 2730 (int) (dim.x * 0.7f), (int) (dim.y * 0.7), 2731 data.getRotation(), MAX_PEEK_BITMAP_PIXELS); 2732 break; 2733 2734 case LocalData.LOCAL_VIDEO: 2735 bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath()); 2736 break; 2737 2738 default: 2739 bitmap = null; 2740 break; 2741 } 2742 2743 if (bitmap == null) { 2744 return; 2745 } 2746 2747 mMainHandler.post(new Runnable() { 2748 @Override 2749 public void run() { 2750 callback.onCallback(bitmap); 2751 } 2752 }); 2753 } 2754 } 2755 2756 private void showDetailsDialog(int dataId) { 2757 final LocalData data = mDataAdapter.getLocalData(dataId); 2758 if (data == null) { 2759 return; 2760 } 2761 MediaDetails details = data.getMediaDetails(getAndroidContext()); 2762 if (details == null) { 2763 return; 2764 } 2765 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details); 2766 detailDialog.show(); 2767 UsageStatistics.instance().mediaInteraction( 2768 fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS, 2769 NavigationChange.InteractionCause.BUTTON, fileAgeFromDataID(dataId)); 2770 } 2771 2772 /** 2773 * Show or hide action bar items depending on current data type. 2774 */ 2775 private void updateActionBarMenu(LocalData data) { 2776 if (mActionBarMenu == null) { 2777 return; 2778 } 2779 2780 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 2781 if (detailsMenuItem == null) { 2782 return; 2783 } 2784 2785 int type = data.getLocalDataType(); 2786 boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO); 2787 detailsMenuItem.setVisible(showDetails); 2788 } 2789} 2790