1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package com.android.camera; 19 20 import android.animation.Animator; 21 import android.annotation.TargetApi; 22 import android.app.ActionBar; 23 import android.app.Activity; 24 import android.app.AlertDialog; 25 import android.app.Dialog; 26 import android.content.ActivityNotFoundException; 27 import android.content.BroadcastReceiver; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.ActivityInfo; 33 import android.content.res.Configuration; 34 import android.graphics.Bitmap; 35 import android.graphics.BitmapFactory; 36 import android.graphics.Matrix; 37 import android.graphics.Point; 38 import android.graphics.RectF; 39 import android.graphics.SurfaceTexture; 40 import android.graphics.drawable.ColorDrawable; 41 import android.graphics.drawable.Drawable; 42 import android.net.Uri; 43 import android.nfc.NfcAdapter; 44 import android.nfc.NfcAdapter.CreateBeamUrisCallback; 45 import android.nfc.NfcEvent; 46 import android.os.AsyncTask; 47 import android.os.Build; 48 import android.os.Bundle; 49 import android.os.Handler; 50 import android.os.HandlerThread; 51 import android.os.Looper; 52 import android.os.Message; 53 import android.provider.MediaStore; 54 import android.provider.Settings; 55 import android.text.TextUtils; 56 import android.util.CameraPerformanceTracker; 57 import android.view.ContextMenu; 58 import android.view.ContextMenu.ContextMenuInfo; 59 import android.view.KeyEvent; 60 import android.view.Menu; 61 import android.view.MenuInflater; 62 import android.view.MenuItem; 63 import android.view.MotionEvent; 64 import android.view.View; 65 import android.view.View.OnSystemUiVisibilityChangeListener; 66 import android.view.ViewGroup; 67 import android.view.Window; 68 import android.view.WindowManager; 69 import android.widget.FrameLayout; 70 import android.widget.ImageView; 71 import android.widget.ShareActionProvider; 72 73 import com.android.camera.app.AppController; 74 import com.android.camera.app.CameraAppUI; 75 import com.android.camera.app.CameraController; 76 import com.android.camera.app.CameraProvider; 77 import com.android.camera.app.CameraServices; 78 import com.android.camera.app.LocationManager; 79 import com.android.camera.app.MemoryManager; 80 import com.android.camera.app.MemoryQuery; 81 import com.android.camera.app.ModuleManager; 82 import com.android.camera.app.ModuleManagerImpl; 83 import com.android.camera.app.MotionManager; 84 import com.android.camera.app.OrientationManager; 85 import com.android.camera.app.OrientationManagerImpl; 86 import com.android.camera.data.CameraDataAdapter; 87 import com.android.camera.data.FixedLastDataAdapter; 88 import com.android.camera.data.LocalData; 89 import com.android.camera.data.LocalDataAdapter; 90 import com.android.camera.data.LocalDataUtil; 91 import com.android.camera.data.LocalDataViewType; 92 import com.android.camera.data.LocalMediaData; 93 import com.android.camera.data.LocalMediaObserver; 94 import com.android.camera.data.LocalSessionData; 95 import com.android.camera.data.MediaDetails; 96 import com.android.camera.data.MetadataLoader; 97 import com.android.camera.data.PanoramaMetadataLoader; 98 import com.android.camera.data.RgbzMetadataLoader; 99 import com.android.camera.data.SimpleViewData; 100 import com.android.camera.debug.Log; 101 import com.android.camera.filmstrip.FilmstripContentPanel; 102 import com.android.camera.filmstrip.FilmstripController; 103 import com.android.camera.hardware.HardwareSpec; 104 import com.android.camera.hardware.HardwareSpecImpl; 105 import com.android.camera.module.ModuleController; 106 import com.android.camera.module.ModulesInfo; 107 import com.android.camera.one.OneCameraManager; 108 import com.android.camera.session.CaptureSession; 109 import com.android.camera.session.CaptureSessionManager; 110 import com.android.camera.session.CaptureSessionManager.SessionListener; 111 import com.android.camera.settings.AppUpgrader; 112 import com.android.camera.settings.CameraSettingsActivity; 113 import com.android.camera.settings.Keys; 114 import com.android.camera.settings.SettingsManager; 115 import com.android.camera.settings.SettingsUtil; 116 import com.android.camera.tinyplanet.TinyPlanetFragment; 117 import com.android.camera.ui.AbstractTutorialOverlay; 118 import com.android.camera.ui.DetailsDialog; 119 import com.android.camera.ui.MainActivityLayout; 120 import com.android.camera.ui.ModeListView; 121 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener; 122 import com.android.camera.ui.PreviewStatusListener; 123 import com.android.camera.util.ApiHelper; 124 import com.android.camera.util.Callback; 125 import com.android.camera.util.CameraUtil; 126 import com.android.camera.util.GalleryHelper; 127 import com.android.camera.util.GcamHelper; 128 import com.android.camera.util.GoogleHelpHelper; 129 import com.android.camera.util.IntentHelper; 130 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 131 import com.android.camera.util.ReleaseDialogHelper; 132 import com.android.camera.util.UsageStatistics; 133 import com.android.camera.widget.FilmstripView; 134 import com.android.camera.widget.Preloader; 135 import com.android.camera2.R; 136 import com.android.ex.camera2.portability.CameraAgent; 137 import com.android.ex.camera2.portability.CameraAgentFactory; 138 import com.android.ex.camera2.portability.CameraSettings; 139 import com.bumptech.glide.Glide; 140 import com.bumptech.glide.GlideBuilder; 141 import com.bumptech.glide.MemoryCategory; 142 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor; 143 import com.google.common.logging.eventprotos; 144 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource; 145 import com.google.common.logging.eventprotos.MediaInteraction; 146 import com.google.common.logging.eventprotos.NavigationChange; 147 148 import java.io.File; 149 import java.io.FileInputStream; 150 import java.io.FileNotFoundException; 151 import java.lang.ref.WeakReference; 152 import java.util.ArrayList; 153 import java.util.HashMap; 154 import java.util.List; 155 import java.util.concurrent.TimeUnit; 156 157 public class CameraActivity extends Activity 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 onCreate(Bundle state) { 1323 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START); 1324 super.onCreate(state); 1325 if (!Glide.isSetup()) { 1326 Glide.setup(new GlideBuilder(this) 1327 .setResizeService(new FifoPriorityThreadPoolExecutor(1))); 1328 Glide.get(this).setMemoryCategory(MemoryCategory.HIGH); 1329 } 1330 1331 mOnCreateTime = System.currentTimeMillis(); 1332 mAppContext = getApplicationContext(); 1333 mSoundPlayer = new SoundPlayer(mAppContext); 1334 1335 mCameraManager = OneCameraManager.get(this); 1336 1337 // TODO: Try to move all the resources allocation to happen as soon as 1338 // possible so we can call module.init() at the earliest time. 1339 mModuleManager = new ModuleManagerImpl(); 1340 GcamHelper.init(getContentResolver()); 1341 ModulesInfo.setupModules(mAppContext, mModuleManager); 1342 1343 mSettingsManager = getServices().getSettingsManager(); 1344 AppUpgrader appUpgrader = new AppUpgrader(this); 1345 appUpgrader.upgrade(mSettingsManager); 1346 Keys.setDefaults(mSettingsManager, mAppContext); 1347 1348 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 1349 setContentView(R.layout.activity_main); 1350 1351 mActionBar = getActionBar(); 1352 // set actionbar background to 100% or 50% transparent 1353 if (ApiHelper.isLOrHigher()) { 1354 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000)); 1355 } else { 1356 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000)); 1357 } 1358 mActionBar.addOnMenuVisibilityListener(mOnMenuVisibilityListener); 1359 1360 mMainHandler = new MainHandler(this, getMainLooper()); 1361 mCameraController = new CameraController(mAppContext, this, mMainHandler, 1362 CameraAgentFactory.getAndroidCameraAgent(this, CameraAgentFactory.CameraApi.API_1), 1363 CameraAgentFactory.getAndroidCameraAgent(this, CameraAgentFactory.CameraApi.AUTO)); 1364 mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback, 1365 mMainHandler); 1366 1367 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); 1368 mModeListView.init(mModuleManager.getSupportedModeIndexList()); 1369 if (ApiHelper.HAS_ROTATION_ANIMATION) { 1370 setRotationAnimation(); 1371 } 1372 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() { 1373 @Override 1374 public void onVisibilityChanged(boolean visible) { 1375 mModeListVisible = visible; 1376 mCameraAppUI.setShutterButtonImportantToA11y(!visible); 1377 updatePreviewVisibility(); 1378 } 1379 }); 1380 1381 // Check if this is in the secure camera mode. 1382 Intent intent = getIntent(); 1383 String action = intent.getAction(); 1384 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 1385 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 1386 mSecureCamera = true; 1387 } else { 1388 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 1389 } 1390 1391 if (mSecureCamera) { 1392 // Change the window flags so that secure camera can show when 1393 // locked 1394 Window win = getWindow(); 1395 WindowManager.LayoutParams params = win.getAttributes(); 1396 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 1397 win.setAttributes(params); 1398 1399 // Filter for screen off so that we can finish secure camera 1400 // activity 1401 // when screen is off. 1402 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); 1403 registerReceiver(mScreenOffReceiver, filter); 1404 } 1405 mCameraAppUI = new CameraAppUI(this, 1406 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent()); 1407 1408 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener); 1409 1410 mAboveFilmstripControlLayout = 1411 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout); 1412 1413 // Add the session listener so we can track the session progress 1414 // updates. 1415 getServices().getCaptureSessionManager().addSessionListener(mSessionListener); 1416 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController(); 1417 mFilmstripController.setImageGap( 1418 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 1419 mPanoramaViewHelper = new PanoramaViewHelper(this); 1420 mPanoramaViewHelper.onCreate(); 1421 // Set up the camera preview first so the preview shows up ASAP. 1422 mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder); 1423 mDataAdapter.setLocalDataListener(mLocalDataListener); 1424 1425 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter, 1426 mDataAdapter); 1427 1428 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener); 1429 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1430 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 1431 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS); 1432 } 1433 1434 mLocationManager = new LocationManager(mAppContext); 1435 1436 mOrientationManager = new OrientationManagerImpl(this); 1437 mOrientationManager.addOnOrientationChangeListener(mMainHandler, this); 1438 1439 setModuleFromModeIndex(getModeIndex()); 1440 mCameraAppUI.prepareModuleUI(); 1441 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent()); 1442 1443 if (!mSecureCamera) { 1444 mFilmstripController.setDataAdapter(mDataAdapter); 1445 if (!isCaptureIntent()) { 1446 mDataAdapter.requestLoad(new Callback<Void>() { 1447 @Override 1448 public void onCallback(Void result) { 1449 fillTemporarySessions(); 1450 } 1451 }); 1452 } 1453 } else { 1454 // Put a lock placeholder as the last image by setting its date to 1455 // 0. 1456 ImageView v = (ImageView) getLayoutInflater().inflate( 1457 R.layout.secure_album_placeholder, null); 1458 v.setTag(R.id.mediadata_tag_viewtype, LocalDataViewType.SECURE_ALBUM_PLACEHOLDER.ordinal()); 1459 v.setOnClickListener(new View.OnClickListener() { 1460 @Override 1461 public void onClick(View view) { 1462 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 1463 NavigationChange.InteractionCause.BUTTON); 1464 startGallery(); 1465 finish(); 1466 } 1467 }); 1468 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera)); 1469 mDataAdapter = new FixedLastDataAdapter( 1470 mAppContext, 1471 mDataAdapter, 1472 new SimpleViewData( 1473 v, 1474 LocalDataViewType.SECURE_ALBUM_PLACEHOLDER, 1475 v.getDrawable().getIntrinsicWidth(), 1476 v.getDrawable().getIntrinsicHeight(), 1477 0, 0)); 1478 // Flush out all the original data. 1479 mDataAdapter.flush(); 1480 mFilmstripController.setDataAdapter(mDataAdapter); 1481 } 1482 1483 setupNfcBeamPush(); 1484 1485 mLocalImagesObserver = new LocalMediaObserver(); 1486 mLocalVideosObserver = new LocalMediaObserver(); 1487 1488 getContentResolver().registerContentObserver( 1489 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1490 mLocalImagesObserver); 1491 getContentResolver().registerContentObserver( 1492 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1493 mLocalVideosObserver); 1494 mMemoryManager = getServices().getMemoryManager(); 1495 1496 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1497 @Override 1498 public void run() { 1499 HashMap memoryData = mMemoryManager.queryMemory(); 1500 UsageStatistics.instance().reportMemoryConsumed(memoryData, 1501 MemoryQuery.REPORT_LABEL_LAUNCH); 1502 } 1503 }); 1504 mMotionManager = getServices().getMotionManager(); 1505 } 1506 1507 /** 1508 * Get the current mode index from the Intent or from persistent 1509 * settings. 1510 */ 1511 public int getModeIndex() { 1512 int modeIndex = -1; 1513 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo); 1514 int videoIndex = getResources().getInteger(R.integer.camera_mode_video); 1515 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1516 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) 1517 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { 1518 modeIndex = videoIndex; 1519 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) { 1520 // Capture intent. 1521 modeIndex = photoIndex; 1522 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 1523 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 1524 .getAction()) 1525 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1526 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1527 Keys.KEY_CAMERA_MODULE_LAST_USED); 1528 1529 // For upgraders who have not seen the aspect ratio selection screen, 1530 // we need to drop them back in the photo module and have them select 1531 // aspect ratio. 1532 // TODO: Move this to SettingsManager as an upgrade procedure. 1533 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1534 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { 1535 modeIndex = photoIndex; 1536 } 1537 } else { 1538 // If the activity has not been started using an explicit intent, 1539 // read the module index from the last time the user changed modes 1540 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1541 Keys.KEY_STARTUP_MODULE_INDEX); 1542 if ((modeIndex == gcamIndex && 1543 !GcamHelper.hasGcamAsSeparateModule()) || modeIndex < 0) { 1544 modeIndex = photoIndex; 1545 } 1546 } 1547 return modeIndex; 1548 } 1549 1550 /** 1551 * Call this whenever the mode drawer or filmstrip change the visibility 1552 * state. 1553 */ 1554 private void updatePreviewVisibility() { 1555 if (mCurrentModule == null) { 1556 return; 1557 } 1558 1559 int visibility = getPreviewVisibility(); 1560 mCameraAppUI.onPreviewVisiblityChanged(visibility); 1561 updatePreviewRendering(visibility); 1562 mCurrentModule.onPreviewVisibilityChanged(visibility); 1563 } 1564 1565 private void updatePreviewRendering(int visibility) { 1566 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1567 mCameraAppUI.pausePreviewRendering(); 1568 } else { 1569 mCameraAppUI.resumePreviewRendering(); 1570 } 1571 } 1572 1573 private int getPreviewVisibility() { 1574 if (mFilmstripCoversPreview) { 1575 return ModuleController.VISIBILITY_HIDDEN; 1576 } else if (mModeListVisible){ 1577 return ModuleController.VISIBILITY_COVERED; 1578 } else { 1579 return ModuleController.VISIBILITY_VISIBLE; 1580 } 1581 } 1582 1583 private void setRotationAnimation() { 1584 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1585 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1586 Window win = getWindow(); 1587 WindowManager.LayoutParams winParams = win.getAttributes(); 1588 winParams.rotationAnimation = rotationAnimation; 1589 win.setAttributes(winParams); 1590 } 1591 1592 @Override 1593 public void onUserInteraction() { 1594 super.onUserInteraction(); 1595 if (!isFinishing()) { 1596 keepScreenOnForAWhile(); 1597 } 1598 } 1599 1600 @Override 1601 public boolean dispatchTouchEvent(MotionEvent ev) { 1602 boolean result = super.dispatchTouchEvent(ev); 1603 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1604 // Real deletion is postponed until the next user interaction after 1605 // the gesture that triggers deletion. Until real deletion is 1606 // performed, users can click the undo button to bring back the 1607 // image that they chose to delete. 1608 if (mPendingDeletion && !mIsUndoingDeletion) { 1609 performDeletion(); 1610 } 1611 } 1612 return result; 1613 } 1614 1615 @Override 1616 public void onPause() { 1617 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE); 1618 1619 /* 1620 * Save the last module index after all secure camera and icon launches, 1621 * not just on mode switches. 1622 * 1623 * Right now we exclude capture intents from this logic, because we also 1624 * ignore the cross-Activity recovery logic in onStart for capture intents. 1625 */ 1626 if (!isCaptureIntent()) { 1627 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 1628 Keys.KEY_STARTUP_MODULE_INDEX, 1629 mCurrentModeIndex); 1630 } 1631 1632 mPaused = true; 1633 mPeekAnimationHandler = null; 1634 mPeekAnimationThread.quitSafely(); 1635 mPeekAnimationThread = null; 1636 1637 // Delete photos that are pending deletion 1638 performDeletion(); 1639 mCurrentModule.pause(); 1640 mOrientationManager.pause(); 1641 mPanoramaViewHelper.onPause(); 1642 1643 mLocalImagesObserver.setForegroundChangeListener(null); 1644 mLocalImagesObserver.setActivityPaused(true); 1645 mLocalVideosObserver.setActivityPaused(true); 1646 mPreloader.cancelAllLoads(); 1647 resetScreenOn(); 1648 1649 mMotionManager.stop(); 1650 1651 UsageStatistics.instance().backgrounded(); 1652 1653 // Close the camera and wait for the operation done. But if we time out 1654 // via RuntimeException, just continue pausing, and request a finish(). 1655 try { 1656 mCameraController.closeCamera(true); 1657 } catch (RuntimeException e) { 1658 Log.e(TAG, "Exception while closing camera", e); 1659 if (!isFinishing()) { 1660 finish(); 1661 } 1662 } 1663 1664 super.onPause(); 1665 } 1666 1667 @Override 1668 public void onResume() { 1669 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME); 1670 Log.v(TAG, "Build info: " + Build.DISPLAY); 1671 1672 mPaused = false; 1673 updateStorageSpaceAndHint(null); 1674 1675 mLastLayoutOrientation = getResources().getConfiguration().orientation; 1676 1677 // TODO: Handle this in OrientationManager. 1678 // Auto-rotate off 1679 if (Settings.System.getInt(getContentResolver(), 1680 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 1681 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 1682 mAutoRotateScreen = false; 1683 } else { 1684 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 1685 mAutoRotateScreen = true; 1686 } 1687 1688 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and 1689 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to 1690 // lockscreen onResume->onPause->onResume sequence. 1691 int source; 1692 String action = getIntent().getAction(); 1693 if (action == null) { 1694 source = ForegroundSource.UNKNOWN_SOURCE; 1695 } else { 1696 switch (action) { 1697 case MediaStore.ACTION_IMAGE_CAPTURE: 1698 source = ForegroundSource.ACTION_IMAGE_CAPTURE; 1699 break; 1700 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA: 1701 // was UNKNOWN_SOURCE in Fishlake. 1702 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA; 1703 break; 1704 case MediaStore.INTENT_ACTION_VIDEO_CAMERA: 1705 // was UNKNOWN_SOURCE in Fishlake. 1706 source = ForegroundSource.ACTION_VIDEO_CAMERA; 1707 break; 1708 case MediaStore.ACTION_VIDEO_CAPTURE: 1709 source = ForegroundSource.ACTION_VIDEO_CAPTURE; 1710 break; 1711 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE: 1712 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake. 1713 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE; 1714 break; 1715 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE: 1716 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE; 1717 break; 1718 case Intent.ACTION_MAIN: 1719 source = ForegroundSource.ACTION_MAIN; 1720 break; 1721 default: 1722 source = ForegroundSource.UNKNOWN_SOURCE; 1723 break; 1724 } 1725 } 1726 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode()); 1727 1728 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext); 1729 if (ApiHelper.isLOrHigher()) { 1730 // hide the up affordance for L devices, it's not very Materially 1731 mActionBar.setDisplayShowHomeEnabled(false); 1732 } 1733 1734 mOrientationManager.resume(); 1735 super.onResume(); 1736 mPeekAnimationThread = new HandlerThread("Peek animation"); 1737 mPeekAnimationThread.start(); 1738 mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper()); 1739 1740 mCurrentModule.hardResetSettings(mSettingsManager); 1741 mCurrentModule.resume(); 1742 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 1743 NavigationChange.InteractionCause.BUTTON); 1744 setSwipingEnabled(true); 1745 1746 if (!mResetToPreviewOnResume) { 1747 LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId()); 1748 if (data != null) { 1749 mDataAdapter.refresh(data.getUri()); 1750 } 1751 } 1752 // The share button might be disabled to avoid double tapping. 1753 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true); 1754 // Default is showing the preview, unless disabled by explicitly 1755 // starting an activity we want to return from to the filmstrip rather 1756 // than the preview. 1757 mResetToPreviewOnResume = true; 1758 1759 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 1760 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 1761 if (!mSecureCamera) { 1762 // If it's secure camera, requestLoad() should not be called 1763 // as it will load all the data. 1764 if (!mFilmstripVisible) { 1765 mDataAdapter.requestLoad(new Callback<Void>() { 1766 @Override 1767 public void onCallback(Void result) { 1768 fillTemporarySessions(); 1769 } 1770 }); 1771 } else { 1772 mDataAdapter.requestLoadNewPhotos(); 1773 } 1774 } 1775 } 1776 mLocalImagesObserver.setActivityPaused(false); 1777 mLocalVideosObserver.setActivityPaused(false); 1778 if (!mSecureCamera) { 1779 mLocalImagesObserver.setForegroundChangeListener( 1780 new LocalMediaObserver.ChangeListener() { 1781 @Override 1782 public void onChange() { 1783 mDataAdapter.requestLoadNewPhotos(); 1784 } 1785 }); 1786 } 1787 1788 keepScreenOnForAWhile(); 1789 1790 // Lights-out mode at all times. 1791 final View rootView = findViewById(R.id.activity_root_view); 1792 mLightsOutRunnable.run(); 1793 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( 1794 new OnSystemUiVisibilityChangeListener() { 1795 @Override 1796 public void onSystemUiVisibilityChange(int visibility) { 1797 mMainHandler.removeCallbacks(mLightsOutRunnable); 1798 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS); 1799 } 1800 }); 1801 1802 mPanoramaViewHelper.onResume(); 1803 ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager); 1804 syncLocationManagerSetting(); 1805 1806 final int previewVisibility = getPreviewVisibility(); 1807 updatePreviewRendering(previewVisibility); 1808 1809 mMotionManager.start(); 1810 } 1811 1812 private void fillTemporarySessions() { 1813 if (mSecureCamera) { 1814 return; 1815 } 1816 // There might be sessions still in flight (processed by our service). 1817 // Make sure they're added to the filmstrip. 1818 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener); 1819 } 1820 1821 @Override 1822 public void onStart() { 1823 super.onStart(); 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 onStop() { 1850 mIsActivityRunning = false; 1851 mPanoramaViewHelper.onStop(); 1852 1853 mLocationManager.disconnect(); 1854 super.onStop(); 1855 } 1856 1857 @Override 1858 public void onDestroy() { 1859 if (mSecureCamera) { 1860 unregisterReceiver(mScreenOffReceiver); 1861 } 1862 mActionBar.removeOnMenuVisibilityListener(mOnMenuVisibilityListener); 1863 mSettingsManager.removeAllListeners(); 1864 mCameraController.removeCallbackReceiver(); 1865 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 1866 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 1867 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener); 1868 mCameraAppUI.onDestroy(); 1869 mModeListView.setVisibilityChangedListener(null); 1870 mCameraController = null; 1871 mSettingsManager = null; 1872 mOrientationManager = null; 1873 mButtonManager = null; 1874 mSoundPlayer.release(); 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 super.onDestroy(); 1882 } 1883 1884 @Override 1885 public void onConfigurationChanged(Configuration config) { 1886 super.onConfigurationChanged(config); 1887 Log.v(TAG, "onConfigurationChanged"); 1888 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 1889 return; 1890 } 1891 1892 if (mLastLayoutOrientation != config.orientation) { 1893 mLastLayoutOrientation = config.orientation; 1894 mCurrentModule.onLayoutOrientationChanged( 1895 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 1896 } 1897 } 1898 1899 @Override 1900 public boolean onKeyDown(int keyCode, KeyEvent event) { 1901 if (!mFilmstripVisible) { 1902 if (mCurrentModule.onKeyDown(keyCode, event)) { 1903 return true; 1904 } 1905 // Prevent software keyboard or voice search from showing up. 1906 if (keyCode == KeyEvent.KEYCODE_SEARCH 1907 || keyCode == KeyEvent.KEYCODE_MENU) { 1908 if (event.isLongPress()) { 1909 return true; 1910 } 1911 } 1912 } 1913 1914 return super.onKeyDown(keyCode, event); 1915 } 1916 1917 @Override 1918 public boolean onKeyUp(int keyCode, KeyEvent event) { 1919 if (!mFilmstripVisible) { 1920 // If a module is in the middle of capture, it should 1921 // consume the key event. 1922 if (mCurrentModule.onKeyUp(keyCode, event)) { 1923 return true; 1924 } else if (keyCode == KeyEvent.KEYCODE_MENU 1925 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 1926 // Let the mode list view consume the event. 1927 mCameraAppUI.openModeList(); 1928 return true; 1929 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 1930 mCameraAppUI.showFilmstrip(); 1931 return true; 1932 } 1933 } else { 1934 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 1935 mFilmstripController.goToNextItem(); 1936 return true; 1937 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 1938 boolean wentToPrevious = mFilmstripController.goToPreviousItem(); 1939 if (!wentToPrevious) { 1940 // at beginning of filmstrip, hide and go back to preview 1941 mCameraAppUI.hideFilmstrip(); 1942 } 1943 return true; 1944 } 1945 } 1946 return super.onKeyUp(keyCode, event); 1947 } 1948 1949 @Override 1950 public void onBackPressed() { 1951 if (!mCameraAppUI.onBackPressed()) { 1952 if (!mCurrentModule.onBackPressed()) { 1953 super.onBackPressed(); 1954 } 1955 } 1956 } 1957 1958 @Override 1959 public boolean isAutoRotateScreen() { 1960 // TODO: Move to OrientationManager. 1961 return mAutoRotateScreen; 1962 } 1963 1964 @Override 1965 public boolean onCreateOptionsMenu(Menu menu) { 1966 MenuInflater inflater = getMenuInflater(); 1967 inflater.inflate(R.menu.filmstrip_menu, menu); 1968 mActionBarMenu = menu; 1969 1970 // add a button for launching the gallery 1971 if (mGalleryIntent != null) { 1972 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent); 1973 if (appName != null) { 1974 MenuItem menuItem = menu.add(appName); 1975 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 1976 menuItem.setIntent(mGalleryIntent); 1977 1978 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent); 1979 if (galleryLogo != null) { 1980 menuItem.setIcon(galleryLogo); 1981 } 1982 } 1983 } 1984 1985 return super.onCreateOptionsMenu(menu); 1986 } 1987 1988 protected long getStorageSpaceBytes() { 1989 synchronized (mStorageSpaceLock) { 1990 return mStorageSpaceBytes; 1991 } 1992 } 1993 1994 protected interface OnStorageUpdateDoneListener { 1995 public void onStorageUpdateDone(long bytes); 1996 } 1997 1998 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) { 1999 /* 2000 * We execute disk operations on a background thread in order to 2001 * free up the UI thread. Synchronizing on the lock below ensures 2002 * that when getStorageSpaceBytes is called, the main thread waits 2003 * until this method has completed. 2004 * 2005 * However, .execute() does not ensure this execution block will be 2006 * run right away (.execute() schedules this AsyncTask for sometime 2007 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 2008 * tries to execute the task in parellel with other AsyncTasks, but 2009 * there's still no guarantee). 2010 * e.g. don't call this then immediately call getStorageSpaceBytes(). 2011 * Instead, pass in an OnStorageUpdateDoneListener. 2012 */ 2013 (new AsyncTask<Void, Void, Long>() { 2014 @Override 2015 protected Long doInBackground(Void ... arg) { 2016 synchronized (mStorageSpaceLock) { 2017 mStorageSpaceBytes = Storage.getAvailableSpace(); 2018 return mStorageSpaceBytes; 2019 } 2020 } 2021 2022 @Override 2023 protected void onPostExecute(Long bytes) { 2024 updateStorageHint(bytes); 2025 // This callback returns after I/O to check disk, so we could be 2026 // pausing and shutting down. If so, don't bother invoking. 2027 if (callback != null && !mPaused) { 2028 callback.onStorageUpdateDone(bytes); 2029 } else { 2030 Log.v(TAG, "ignoring storage callback after activity pause"); 2031 } 2032 } 2033 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2034 } 2035 2036 protected void updateStorageHint(long storageSpace) { 2037 if (!mIsActivityRunning) { 2038 return; 2039 } 2040 2041 String message = null; 2042 if (storageSpace == Storage.UNAVAILABLE) { 2043 message = getString(R.string.no_storage); 2044 } else if (storageSpace == Storage.PREPARING) { 2045 message = getString(R.string.preparing_sd); 2046 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 2047 message = getString(R.string.access_sd_fail); 2048 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 2049 message = getString(R.string.spaceIsLow_content); 2050 } 2051 2052 if (message != null) { 2053 Log.w(TAG, "Storage warning: " + message); 2054 if (mStorageHint == null) { 2055 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message); 2056 } else { 2057 mStorageHint.setText(message); 2058 } 2059 mStorageHint.show(); 2060 UsageStatistics.instance().storageWarning(storageSpace); 2061 } else if (mStorageHint != null) { 2062 mStorageHint.cancel(); 2063 mStorageHint = null; 2064 } 2065 } 2066 2067 protected void setResultEx(int resultCode) { 2068 mResultCodeForTesting = resultCode; 2069 setResult(resultCode); 2070 } 2071 2072 protected void setResultEx(int resultCode, Intent data) { 2073 mResultCodeForTesting = resultCode; 2074 mResultDataForTesting = data; 2075 setResult(resultCode, data); 2076 } 2077 2078 public int getResultCode() { 2079 return mResultCodeForTesting; 2080 } 2081 2082 public Intent getResultData() { 2083 return mResultDataForTesting; 2084 } 2085 2086 public boolean isSecureCamera() { 2087 return mSecureCamera; 2088 } 2089 2090 @Override 2091 public boolean isPaused() { 2092 return mPaused; 2093 } 2094 2095 @Override 2096 public int getPreferredChildModeIndex(int modeIndex) { 2097 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2098 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager); 2099 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) { 2100 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 2101 } 2102 } 2103 return modeIndex; 2104 } 2105 2106 @Override 2107 public void onModeSelected(int modeIndex) { 2108 if (mCurrentModeIndex == modeIndex) { 2109 return; 2110 } 2111 2112 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 2113 // Record last used camera mode for quick switching 2114 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 2115 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2116 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2117 Keys.KEY_CAMERA_MODULE_LAST_USED, 2118 modeIndex); 2119 } 2120 2121 closeModule(mCurrentModule); 2122 2123 // Select the correct module index from the mode switcher index. 2124 modeIndex = getPreferredChildModeIndex(modeIndex); 2125 setModuleFromModeIndex(modeIndex); 2126 2127 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 2128 mCameraAppUI.addShutterListener(mCurrentModule); 2129 mCameraAppUI.hideLetterboxing(); 2130 openModule(mCurrentModule); 2131 mCurrentModule.onOrientationChanged(mLastRawOrientation); 2132 // Store the module index so we can use it the next time the Camera 2133 // starts up. 2134 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2135 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex); 2136 } 2137 2138 /** 2139 * Shows the settings dialog. 2140 */ 2141 @Override 2142 public void onSettingsSelected() { 2143 UsageStatistics.instance().controlUsed( 2144 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 2145 Intent intent = new Intent(this, CameraSettingsActivity.class); 2146 startActivity(intent); 2147 } 2148 2149 @Override 2150 public void freezeScreenUntilPreviewReady() { 2151 mCameraAppUI.freezeScreenUntilPreviewReady(); 2152 } 2153 2154 /** 2155 * Sets the mCurrentModuleIndex, creates a new module instance for the given 2156 * index an sets it as mCurrentModule. 2157 */ 2158 private void setModuleFromModeIndex(int modeIndex) { 2159 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2160 if (agent == null) { 2161 return; 2162 } 2163 if (!agent.requestAppForCamera()) { 2164 mCameraController.closeCamera(true); 2165 } 2166 mCurrentModeIndex = agent.getModuleId(); 2167 mCurrentModule = (CameraModule) agent.createModule(this); 2168 } 2169 2170 @Override 2171 public SettingsManager getSettingsManager() { 2172 return mSettingsManager; 2173 } 2174 2175 @Override 2176 public CameraServices getServices() { 2177 return (CameraServices) getApplication(); 2178 } 2179 2180 public List<String> getSupportedModeNames() { 2181 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 2182 List<String> supported = new ArrayList<String>(); 2183 2184 for (Integer modeIndex : indices) { 2185 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 2186 if (name != null && !name.equals("")) { 2187 supported.add(name); 2188 } 2189 } 2190 return supported; 2191 } 2192 2193 @Override 2194 public ButtonManager getButtonManager() { 2195 if (mButtonManager == null) { 2196 mButtonManager = new ButtonManager(this); 2197 } 2198 return mButtonManager; 2199 } 2200 2201 @Override 2202 public SoundPlayer getSoundPlayer() { 2203 return mSoundPlayer; 2204 } 2205 2206 /** 2207 * Creates an AlertDialog appropriate for choosing whether to enable 2208 * location on the first run of the app. 2209 */ 2210 public AlertDialog getFirstTimeLocationAlert() { 2211 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2212 builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() { 2213 @Override 2214 public void onCallback(Boolean locationOn) { 2215 Keys.setLocation(mSettingsManager, locationOn, mLocationManager); 2216 } 2217 }); 2218 if (builder != null) { 2219 return builder.create(); 2220 } else { 2221 return null; 2222 } 2223 } 2224 2225 /** 2226 * Launches an ACTION_EDIT intent for the given local data item. If 2227 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2228 * the user start either the tiny planet editor or another photo edior. 2229 * 2230 * @param data The data item to edit. 2231 */ 2232 public void launchEditor(LocalData data) { 2233 Intent intent = new Intent(Intent.ACTION_EDIT) 2234 .setDataAndType(data.getUri(), data.getMimeType()) 2235 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2236 try { 2237 launchActivityByIntent(intent); 2238 } catch (ActivityNotFoundException e) { 2239 final String msgEditWith = getResources().getString(R.string.edit_with); 2240 launchActivityByIntent(Intent.createChooser(intent, msgEditWith)); 2241 } 2242 } 2243 2244 @Override 2245 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2246 super.onCreateContextMenu(menu, v, menuInfo); 2247 2248 MenuInflater inflater = getMenuInflater(); 2249 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2250 } 2251 2252 @Override 2253 public boolean onContextItemSelected(MenuItem item) { 2254 switch (item.getItemId()) { 2255 case R.id.tiny_planet_editor: 2256 mMyFilmstripBottomControlListener.onTinyPlanet(); 2257 return true; 2258 case R.id.photo_editor: 2259 mMyFilmstripBottomControlListener.onEdit(); 2260 return true; 2261 } 2262 return false; 2263 } 2264 2265 /** 2266 * Launch the tiny planet editor. 2267 * 2268 * @param data The data must be a 360 degree stereographically mapped 2269 * panoramic image. It will not be modified, instead a new item 2270 * with the result will be added to the filmstrip. 2271 */ 2272 public void launchTinyPlanetEditor(LocalData data) { 2273 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2274 Bundle bundle = new Bundle(); 2275 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString()); 2276 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 2277 fragment.setArguments(bundle); 2278 fragment.show(getFragmentManager(), "tiny_planet"); 2279 } 2280 2281 /** 2282 * Returns what UI mode (capture mode or filmstrip) we are in. 2283 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2284 */ 2285 private int currentUserInterfaceMode() { 2286 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2287 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2288 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2289 } 2290 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2291 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2292 } 2293 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2294 mode = NavigationChange.Mode.LENS_BLUR; 2295 } 2296 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2297 mode = NavigationChange.Mode.HDR_PLUS; 2298 } 2299 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2300 mode = NavigationChange.Mode.PHOTO_SPHERE; 2301 } 2302 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2303 mode = NavigationChange.Mode.PANORAMA; 2304 } 2305 if (mFilmstripVisible) { 2306 mode = NavigationChange.Mode.FILMSTRIP; 2307 } 2308 return mode; 2309 } 2310 2311 private void openModule(CameraModule module) { 2312 module.init(this, isSecureCamera(), isCaptureIntent()); 2313 module.hardResetSettings(mSettingsManager); 2314 module.resume(); 2315 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2316 NavigationChange.InteractionCause.BUTTON); 2317 updatePreviewVisibility(); 2318 } 2319 2320 private void closeModule(CameraModule module) { 2321 module.pause(); 2322 mCameraAppUI.clearModuleUI(); 2323 } 2324 2325 private void performDeletion() { 2326 if (!mPendingDeletion) { 2327 return; 2328 } 2329 hideUndoDeletionBar(false); 2330 mDataAdapter.executeDeletion(); 2331 } 2332 2333 public void showUndoDeletionBar() { 2334 if (mPendingDeletion) { 2335 performDeletion(); 2336 } 2337 Log.v(TAG, "showing undo bar"); 2338 mPendingDeletion = true; 2339 if (mUndoDeletionBar == null) { 2340 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2341 mAboveFilmstripControlLayout, true); 2342 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2343 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2344 button.setOnClickListener(new View.OnClickListener() { 2345 @Override 2346 public void onClick(View view) { 2347 mDataAdapter.undoDataRemoval(); 2348 hideUndoDeletionBar(true); 2349 } 2350 }); 2351 // Setting undo bar clickable to avoid touch events going through 2352 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2353 mUndoDeletionBar.setClickable(true); 2354 // When there is user interaction going on with the undo button, we 2355 // do not want to hide the undo bar. 2356 button.setOnTouchListener(new View.OnTouchListener() { 2357 @Override 2358 public boolean onTouch(View v, MotionEvent event) { 2359 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2360 mIsUndoingDeletion = true; 2361 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2362 mIsUndoingDeletion = false; 2363 } 2364 return false; 2365 } 2366 }); 2367 } 2368 mUndoDeletionBar.setAlpha(0f); 2369 mUndoDeletionBar.setVisibility(View.VISIBLE); 2370 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2371 } 2372 2373 private void hideUndoDeletionBar(boolean withAnimation) { 2374 Log.v(TAG, "Hiding undo deletion bar"); 2375 mPendingDeletion = false; 2376 if (mUndoDeletionBar != null) { 2377 if (withAnimation) { 2378 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2379 .setListener(new Animator.AnimatorListener() { 2380 @Override 2381 public void onAnimationStart(Animator animation) { 2382 // Do nothing. 2383 } 2384 2385 @Override 2386 public void onAnimationEnd(Animator animation) { 2387 mUndoDeletionBar.setVisibility(View.GONE); 2388 } 2389 2390 @Override 2391 public void onAnimationCancel(Animator animation) { 2392 // Do nothing. 2393 } 2394 2395 @Override 2396 public void onAnimationRepeat(Animator animation) { 2397 // Do nothing. 2398 } 2399 }).start(); 2400 } else { 2401 mUndoDeletionBar.setVisibility(View.GONE); 2402 } 2403 } 2404 } 2405 2406 @Override 2407 public void onOrientationChanged(int orientation) { 2408 // We keep the last known orientation. So if the user first orient 2409 // the camera then point the camera to floor or sky, we still have 2410 // the correct orientation. 2411 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) { 2412 return; 2413 } 2414 mLastRawOrientation = orientation; 2415 if (mCurrentModule != null) { 2416 mCurrentModule.onOrientationChanged(orientation); 2417 } 2418 } 2419 2420 /** 2421 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2422 * capture intent. 2423 * 2424 * @param enable {@code true} to enable swipe. 2425 */ 2426 public void setSwipingEnabled(boolean enable) { 2427 // TODO: Bring back the functionality. 2428 if (isCaptureIntent()) { 2429 // lockPreview(true); 2430 } else { 2431 // lockPreview(!enable); 2432 } 2433 } 2434 2435 // Accessor methods for getting latency times used in performance testing 2436 public long getFirstPreviewTime() { 2437 if (mCurrentModule instanceof PhotoModule) { 2438 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2439 if (coverHiddenTime != -1) { 2440 return coverHiddenTime - mOnCreateTime; 2441 } 2442 } 2443 return -1; 2444 } 2445 2446 public long getAutoFocusTime() { 2447 return (mCurrentModule instanceof PhotoModule) ? 2448 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2449 } 2450 2451 public long getShutterLag() { 2452 return (mCurrentModule instanceof PhotoModule) ? 2453 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2454 } 2455 2456 public long getShutterToPictureDisplayedTime() { 2457 return (mCurrentModule instanceof PhotoModule) ? 2458 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2459 } 2460 2461 public long getPictureDisplayedToJpegCallbackTime() { 2462 return (mCurrentModule instanceof PhotoModule) ? 2463 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2464 } 2465 2466 public long getJpegCallbackFinishTime() { 2467 return (mCurrentModule instanceof PhotoModule) ? 2468 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2469 } 2470 2471 public long getCaptureStartTime() { 2472 return (mCurrentModule instanceof PhotoModule) ? 2473 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2474 } 2475 2476 public boolean isRecording() { 2477 return (mCurrentModule instanceof VideoModule) ? 2478 ((VideoModule) mCurrentModule).isRecording() : false; 2479 } 2480 2481 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() { 2482 return mCameraController; 2483 } 2484 2485 // For debugging purposes only. 2486 public CameraModule getCurrentModule() { 2487 return mCurrentModule; 2488 } 2489 2490 @Override 2491 public void showTutorial(AbstractTutorialOverlay tutorial) { 2492 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2493 } 2494 2495 @Override 2496 public void showErrorAndFinish(int messageId) { 2497 CameraUtil.showErrorAndFinish(this, messageId); 2498 } 2499 2500 /** 2501 * Reads the current location recording settings and passes it on to the 2502 * location manager. 2503 */ 2504 public void syncLocationManagerSetting() { 2505 Keys.syncLocationManager(mSettingsManager, mLocationManager); 2506 } 2507 2508 private void keepScreenOnForAWhile() { 2509 if (mKeepScreenOn) { 2510 return; 2511 } 2512 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2513 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2514 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2515 } 2516 2517 private void resetScreenOn() { 2518 mKeepScreenOn = false; 2519 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2520 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2521 } 2522 2523 /** 2524 * @return {@code true} if the Gallery is launched successfully. 2525 */ 2526 private boolean startGallery() { 2527 if (mGalleryIntent == null) { 2528 return false; 2529 } 2530 try { 2531 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2532 NavigationChange.InteractionCause.BUTTON); 2533 Intent startGalleryIntent = new Intent(mGalleryIntent); 2534 int currentDataId = mFilmstripController.getCurrentId(); 2535 LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId); 2536 if (currentLocalData != null) { 2537 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri()); 2538 } 2539 launchActivityByIntent(startGalleryIntent); 2540 } catch (ActivityNotFoundException e) { 2541 Log.w(TAG, "Failed to launch gallery activity, closing"); 2542 } 2543 return false; 2544 } 2545 2546 private void setNfcBeamPushUriFromData(LocalData data) { 2547 final Uri uri = data.getUri(); 2548 if (uri != Uri.EMPTY) { 2549 mNfcPushUris[0] = uri; 2550 } else { 2551 mNfcPushUris[0] = null; 2552 } 2553 } 2554 2555 /** 2556 * Updates the visibility of the filmstrip bottom controls and action bar. 2557 */ 2558 private void updateUiByData(final int dataId) { 2559 final LocalData currentData = mDataAdapter.getLocalData(dataId); 2560 if (currentData == null) { 2561 Log.w(TAG, "Current data ID not found."); 2562 hideSessionProgress(); 2563 return; 2564 } 2565 updateActionBarMenu(currentData); 2566 2567 /* Bottom controls. */ 2568 updateBottomControlsByData(currentData); 2569 2570 if (isSecureCamera()) { 2571 // We cannot show buttons in secure camera since go to other 2572 // activities might create a security hole. 2573 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2574 return; 2575 } 2576 2577 2578 setNfcBeamPushUriFromData(currentData); 2579 2580 if (!mDataAdapter.isMetadataUpdated(dataId)) { 2581 mDataAdapter.updateMetadata(dataId); 2582 } 2583 } 2584 2585 /** 2586 * Updates the bottom controls based on the data. 2587 */ 2588 private void updateBottomControlsByData(final LocalData currentData) { 2589 2590 final CameraAppUI.BottomPanel filmstripBottomPanel = 2591 mCameraAppUI.getFilmstripBottomControls(); 2592 filmstripBottomPanel.showControls(); 2593 filmstripBottomPanel.setEditButtonVisibility( 2594 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT)); 2595 filmstripBottomPanel.setShareButtonVisibility( 2596 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE)); 2597 filmstripBottomPanel.setDeleteButtonVisibility( 2598 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE)); 2599 2600 /* Progress bar */ 2601 2602 Uri contentUri = currentData.getUri(); 2603 CaptureSessionManager sessionManager = getServices() 2604 .getCaptureSessionManager(); 2605 2606 if (sessionManager.hasErrorMessage(contentUri)) { 2607 showProcessError(sessionManager.getErrorMesage(contentUri)); 2608 } else { 2609 filmstripBottomPanel.hideProgressError(); 2610 CaptureSession session = sessionManager.getSession(contentUri); 2611 2612 if (session != null) { 2613 int sessionProgress = session.getProgress(); 2614 2615 if (sessionProgress < 0) { 2616 hideSessionProgress(); 2617 } else { 2618 CharSequence progressMessage = session.getProgressMessage(); 2619 showSessionProgress(progressMessage); 2620 updateSessionProgress(sessionProgress); 2621 } 2622 } else { 2623 hideSessionProgress(); 2624 } 2625 } 2626 2627 /* View button */ 2628 2629 // We need to add this to a separate DB. 2630 final int viewButtonVisibility; 2631 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) { 2632 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2633 } else if (RgbzMetadataLoader.hasRGBZData(currentData)) { 2634 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 2635 } else { 2636 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 2637 } 2638 2639 filmstripBottomPanel.setTinyPlanetEnabled( 2640 PanoramaMetadataLoader.isPanorama360(currentData)); 2641 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 2642 } 2643 2644 private class PeekAnimationHandler extends Handler { 2645 private class DataAndCallback { 2646 LocalData mData; 2647 com.android.camera.util.Callback<Bitmap> mCallback; 2648 2649 public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap> 2650 callback) { 2651 mData = data; 2652 mCallback = callback; 2653 } 2654 } 2655 2656 public PeekAnimationHandler(Looper looper) { 2657 super(looper); 2658 } 2659 2660 /** 2661 * Starts the animation decoding job and posts a {@code Runnable} back 2662 * when when the decoding is done. 2663 * 2664 * @param data The data item to decode the thumbnail for. 2665 * @param callback {@link com.android.camera.util.Callback} after the 2666 * decoding is done. 2667 */ 2668 public void startDecodingJob(final LocalData data, 2669 final com.android.camera.util.Callback<Bitmap> callback) { 2670 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/, 2671 new DataAndCallback(data, callback)).sendToTarget(); 2672 } 2673 2674 @Override 2675 public void handleMessage(Message msg) { 2676 final LocalData data = ((DataAndCallback) msg.obj).mData; 2677 final com.android.camera.util.Callback<Bitmap> callback = 2678 ((DataAndCallback) msg.obj).mCallback; 2679 if (data == null || callback == null) { 2680 return; 2681 } 2682 2683 final Bitmap bitmap; 2684 switch (data.getLocalDataType()) { 2685 case LocalData.LOCAL_IN_PROGRESS_DATA: 2686 byte[] jpegData = Storage.getJpegForSession(data.getUri()); 2687 if (jpegData != null) { 2688 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 2689 } else { 2690 bitmap = null; 2691 } 2692 break; 2693 2694 case LocalData.LOCAL_IMAGE: 2695 FileInputStream stream; 2696 try { 2697 stream = new FileInputStream(data.getPath()); 2698 } catch (FileNotFoundException e) { 2699 Log.e(TAG, "File not found:" + data.getPath()); 2700 return; 2701 } 2702 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(), 2703 data.getRotation(), mAboveFilmstripControlLayout.getWidth(), 2704 mAboveFilmstripControlLayout.getMeasuredHeight()); 2705 if (data.getRotation() % 180 != 0) { 2706 int dummy = dim.x; 2707 dim.x = dim.y; 2708 dim.y = dummy; 2709 } 2710 bitmap = LocalDataUtil 2711 .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(), 2712 (int) (dim.x * 0.7f), (int) (dim.y * 0.7), 2713 data.getRotation(), MAX_PEEK_BITMAP_PIXELS); 2714 break; 2715 2716 case LocalData.LOCAL_VIDEO: 2717 bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath()); 2718 break; 2719 2720 default: 2721 bitmap = null; 2722 break; 2723 } 2724 2725 if (bitmap == null) { 2726 return; 2727 } 2728 2729 mMainHandler.post(new Runnable() { 2730 @Override 2731 public void run() { 2732 callback.onCallback(bitmap); 2733 } 2734 }); 2735 } 2736 } 2737 2738 private void showDetailsDialog(int dataId) { 2739 final LocalData data = mDataAdapter.getLocalData(dataId); 2740 if (data == null) { 2741 return; 2742 } 2743 MediaDetails details = data.getMediaDetails(getAndroidContext()); 2744 if (details == null) { 2745 return; 2746 } 2747 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details); 2748 detailDialog.show(); 2749 UsageStatistics.instance().mediaInteraction( 2750 fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS, 2751 NavigationChange.InteractionCause.BUTTON, fileAgeFromDataID(dataId)); 2752 } 2753 2754 /** 2755 * Show or hide action bar items depending on current data type. 2756 */ 2757 private void updateActionBarMenu(LocalData data) { 2758 if (mActionBarMenu == null) { 2759 return; 2760 } 2761 2762 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 2763 if (detailsMenuItem == null) { 2764 return; 2765 } 2766 2767 int type = data.getLocalDataType(); 2768 boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO); 2769 detailsMenuItem.setVisible(showDetails); 2770 } 2771 } 2772