CameraActivity.java revision 365ef4ca63d2c3e988cb54887f0f58a33d92aacc
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package com.android.camera; 19 20import android.animation.Animator; 21import android.annotation.TargetApi; 22import android.app.ActionBar; 23import android.app.Activity; 24import android.app.AlertDialog; 25import android.app.Dialog; 26import android.content.ActivityNotFoundException; 27import android.content.BroadcastReceiver; 28import android.content.ContentResolver; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.pm.ActivityInfo; 33import android.content.res.Configuration; 34import android.graphics.Bitmap; 35import android.graphics.BitmapFactory; 36import android.graphics.Matrix; 37import android.graphics.Point; 38import android.graphics.RectF; 39import android.graphics.SurfaceTexture; 40import android.graphics.drawable.ColorDrawable; 41import android.graphics.drawable.Drawable; 42import android.net.Uri; 43import android.nfc.NfcAdapter; 44import android.nfc.NfcAdapter.CreateBeamUrisCallback; 45import android.nfc.NfcEvent; 46import android.os.AsyncTask; 47import android.os.Build; 48import android.os.Bundle; 49import android.os.Handler; 50import android.os.HandlerThread; 51import android.os.Looper; 52import android.os.Message; 53import android.provider.MediaStore; 54import android.provider.Settings; 55import android.text.TextUtils; 56import android.util.CameraPerformanceTracker; 57import android.view.ContextMenu; 58import android.view.ContextMenu.ContextMenuInfo; 59import android.view.KeyEvent; 60import android.view.Menu; 61import android.view.MenuInflater; 62import android.view.MenuItem; 63import android.view.MotionEvent; 64import android.view.View; 65import android.view.View.OnSystemUiVisibilityChangeListener; 66import android.view.ViewGroup; 67import android.view.Window; 68import android.view.WindowManager; 69import android.widget.FrameLayout; 70import android.widget.ImageView; 71import android.widget.ShareActionProvider; 72 73import com.android.camera.app.AppController; 74import com.android.camera.app.CameraAppUI; 75import com.android.camera.app.CameraController; 76import com.android.camera.app.CameraProvider; 77import com.android.camera.app.CameraServices; 78import com.android.camera.app.LocationManager; 79import com.android.camera.app.MemoryManager; 80import com.android.camera.app.MemoryQuery; 81import com.android.camera.app.ModuleManager; 82import com.android.camera.app.ModuleManagerImpl; 83import com.android.camera.app.MotionManager; 84import com.android.camera.app.OrientationManager; 85import com.android.camera.app.OrientationManagerImpl; 86import com.android.camera.data.CameraDataAdapter; 87import com.android.camera.data.FixedLastDataAdapter; 88import com.android.camera.data.LocalData; 89import com.android.camera.data.LocalDataAdapter; 90import com.android.camera.data.LocalDataUtil; 91import com.android.camera.data.LocalDataViewType; 92import com.android.camera.data.LocalMediaData; 93import com.android.camera.data.LocalMediaObserver; 94import com.android.camera.data.LocalSessionData; 95import com.android.camera.data.MediaDetails; 96import com.android.camera.data.MetadataLoader; 97import com.android.camera.data.PanoramaMetadataLoader; 98import com.android.camera.data.RgbzMetadataLoader; 99import com.android.camera.data.SimpleViewData; 100import com.android.camera.debug.Log; 101import com.android.camera.filmstrip.FilmstripContentPanel; 102import com.android.camera.filmstrip.FilmstripController; 103import com.android.camera.hardware.HardwareSpec; 104import com.android.camera.hardware.HardwareSpecImpl; 105import com.android.camera.module.ModuleController; 106import com.android.camera.module.ModulesInfo; 107import com.android.camera.one.OneCameraManager; 108import com.android.camera.session.CaptureSession; 109import com.android.camera.session.CaptureSessionManager; 110import com.android.camera.session.CaptureSessionManager.SessionListener; 111import com.android.camera.settings.AppUpgrader; 112import com.android.camera.settings.CameraSettingsActivity; 113import com.android.camera.settings.Keys; 114import com.android.camera.settings.SettingsManager; 115import com.android.camera.settings.SettingsUtil; 116import com.android.camera.tinyplanet.TinyPlanetFragment; 117import com.android.camera.ui.AbstractTutorialOverlay; 118import com.android.camera.ui.DetailsDialog; 119import com.android.camera.ui.MainActivityLayout; 120import com.android.camera.ui.ModeListView; 121import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener; 122import com.android.camera.ui.PreviewStatusListener; 123import com.android.camera.util.ApiHelper; 124import com.android.camera.util.Callback; 125import com.android.camera.util.CameraUtil; 126import com.android.camera.util.GalleryHelper; 127import com.android.camera.util.GcamHelper; 128import com.android.camera.util.GoogleHelpHelper; 129import com.android.camera.util.IntentHelper; 130import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 131import com.android.camera.util.ReleaseDialogHelper; 132import com.android.camera.util.UsageStatistics; 133import com.android.camera.widget.FilmstripView; 134import com.android.camera.widget.Preloader; 135import com.android.camera2.R; 136import com.android.ex.camera2.portability.CameraAgent; 137import com.android.ex.camera2.portability.CameraAgentFactory; 138import com.android.ex.camera2.portability.CameraSettings; 139import com.bumptech.glide.Glide; 140import com.bumptech.glide.GlideBuilder; 141import com.bumptech.glide.MemoryCategory; 142import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor; 143import com.google.common.logging.eventprotos; 144import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource; 145import com.google.common.logging.eventprotos.MediaInteraction; 146import com.google.common.logging.eventprotos.NavigationChange; 147 148import java.io.File; 149import java.io.FileInputStream; 150import java.io.FileNotFoundException; 151import java.lang.ref.WeakReference; 152import java.util.ArrayList; 153import java.util.HashMap; 154import java.util.List; 155import java.util.concurrent.TimeUnit; 156 157public class CameraActivity extends Activity 158 implements AppController, CameraAgent.CameraOpenCallback, 159 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(2))); 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 @Override 1989 public boolean onPrepareOptionsMenu(Menu menu) { 1990 if (isSecureCamera() && !ApiHelper.isLOrHigher()) { 1991 // Compatibility pre-L: launching new activities right above 1992 // lockscreen does not reliably work, only show help if not secure 1993 menu.removeItem(R.id.action_help_and_feedback); 1994 } 1995 1996 return super.onPrepareOptionsMenu(menu); 1997 } 1998 1999 protected long getStorageSpaceBytes() { 2000 synchronized (mStorageSpaceLock) { 2001 return mStorageSpaceBytes; 2002 } 2003 } 2004 2005 protected interface OnStorageUpdateDoneListener { 2006 public void onStorageUpdateDone(long bytes); 2007 } 2008 2009 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) { 2010 /* 2011 * We execute disk operations on a background thread in order to 2012 * free up the UI thread. Synchronizing on the lock below ensures 2013 * that when getStorageSpaceBytes is called, the main thread waits 2014 * until this method has completed. 2015 * 2016 * However, .execute() does not ensure this execution block will be 2017 * run right away (.execute() schedules this AsyncTask for sometime 2018 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 2019 * tries to execute the task in parellel with other AsyncTasks, but 2020 * there's still no guarantee). 2021 * e.g. don't call this then immediately call getStorageSpaceBytes(). 2022 * Instead, pass in an OnStorageUpdateDoneListener. 2023 */ 2024 (new AsyncTask<Void, Void, Long>() { 2025 @Override 2026 protected Long doInBackground(Void ... arg) { 2027 synchronized (mStorageSpaceLock) { 2028 mStorageSpaceBytes = Storage.getAvailableSpace(); 2029 return mStorageSpaceBytes; 2030 } 2031 } 2032 2033 @Override 2034 protected void onPostExecute(Long bytes) { 2035 updateStorageHint(bytes); 2036 // This callback returns after I/O to check disk, so we could be 2037 // pausing and shutting down. If so, don't bother invoking. 2038 if (callback != null && !mPaused) { 2039 callback.onStorageUpdateDone(bytes); 2040 } else { 2041 Log.v(TAG, "ignoring storage callback after activity pause"); 2042 } 2043 } 2044 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2045 } 2046 2047 protected void updateStorageHint(long storageSpace) { 2048 if (!mIsActivityRunning) { 2049 return; 2050 } 2051 2052 String message = null; 2053 if (storageSpace == Storage.UNAVAILABLE) { 2054 message = getString(R.string.no_storage); 2055 } else if (storageSpace == Storage.PREPARING) { 2056 message = getString(R.string.preparing_sd); 2057 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 2058 message = getString(R.string.access_sd_fail); 2059 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 2060 message = getString(R.string.spaceIsLow_content); 2061 } 2062 2063 if (message != null) { 2064 Log.w(TAG, "Storage warning: " + message); 2065 if (mStorageHint == null) { 2066 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message); 2067 } else { 2068 mStorageHint.setText(message); 2069 } 2070 mStorageHint.show(); 2071 UsageStatistics.instance().storageWarning(storageSpace); 2072 } else if (mStorageHint != null) { 2073 mStorageHint.cancel(); 2074 mStorageHint = null; 2075 } 2076 } 2077 2078 protected void setResultEx(int resultCode) { 2079 mResultCodeForTesting = resultCode; 2080 setResult(resultCode); 2081 } 2082 2083 protected void setResultEx(int resultCode, Intent data) { 2084 mResultCodeForTesting = resultCode; 2085 mResultDataForTesting = data; 2086 setResult(resultCode, data); 2087 } 2088 2089 public int getResultCode() { 2090 return mResultCodeForTesting; 2091 } 2092 2093 public Intent getResultData() { 2094 return mResultDataForTesting; 2095 } 2096 2097 public boolean isSecureCamera() { 2098 return mSecureCamera; 2099 } 2100 2101 @Override 2102 public boolean isPaused() { 2103 return mPaused; 2104 } 2105 2106 @Override 2107 public int getPreferredChildModeIndex(int modeIndex) { 2108 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2109 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager); 2110 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) { 2111 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 2112 } 2113 } 2114 return modeIndex; 2115 } 2116 2117 @Override 2118 public void onModeSelected(int modeIndex) { 2119 if (mCurrentModeIndex == modeIndex) { 2120 return; 2121 } 2122 2123 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 2124 // Record last used camera mode for quick switching 2125 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 2126 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2127 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2128 Keys.KEY_CAMERA_MODULE_LAST_USED, 2129 modeIndex); 2130 } 2131 2132 closeModule(mCurrentModule); 2133 2134 // Select the correct module index from the mode switcher index. 2135 modeIndex = getPreferredChildModeIndex(modeIndex); 2136 setModuleFromModeIndex(modeIndex); 2137 2138 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 2139 mCameraAppUI.addShutterListener(mCurrentModule); 2140 mCameraAppUI.hideLetterboxing(); 2141 openModule(mCurrentModule); 2142 mCurrentModule.onOrientationChanged(mLastRawOrientation); 2143 // Store the module index so we can use it the next time the Camera 2144 // starts up. 2145 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2146 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex); 2147 } 2148 2149 /** 2150 * Shows the settings dialog. 2151 */ 2152 @Override 2153 public void onSettingsSelected() { 2154 UsageStatistics.instance().controlUsed( 2155 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 2156 Intent intent = new Intent(this, CameraSettingsActivity.class); 2157 startActivity(intent); 2158 } 2159 2160 @Override 2161 public void freezeScreenUntilPreviewReady() { 2162 mCameraAppUI.freezeScreenUntilPreviewReady(); 2163 } 2164 2165 /** 2166 * Sets the mCurrentModuleIndex, creates a new module instance for the given 2167 * index an sets it as mCurrentModule. 2168 */ 2169 private void setModuleFromModeIndex(int modeIndex) { 2170 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2171 if (agent == null) { 2172 return; 2173 } 2174 if (!agent.requestAppForCamera()) { 2175 mCameraController.closeCamera(true); 2176 } 2177 mCurrentModeIndex = agent.getModuleId(); 2178 mCurrentModule = (CameraModule) agent.createModule(this); 2179 } 2180 2181 @Override 2182 public SettingsManager getSettingsManager() { 2183 return mSettingsManager; 2184 } 2185 2186 @Override 2187 public CameraServices getServices() { 2188 return (CameraServices) getApplication(); 2189 } 2190 2191 public List<String> getSupportedModeNames() { 2192 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 2193 List<String> supported = new ArrayList<String>(); 2194 2195 for (Integer modeIndex : indices) { 2196 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 2197 if (name != null && !name.equals("")) { 2198 supported.add(name); 2199 } 2200 } 2201 return supported; 2202 } 2203 2204 @Override 2205 public ButtonManager getButtonManager() { 2206 if (mButtonManager == null) { 2207 mButtonManager = new ButtonManager(this); 2208 } 2209 return mButtonManager; 2210 } 2211 2212 @Override 2213 public SoundPlayer getSoundPlayer() { 2214 return mSoundPlayer; 2215 } 2216 2217 /** 2218 * Creates an AlertDialog appropriate for choosing whether to enable 2219 * location on the first run of the app. 2220 */ 2221 public AlertDialog getFirstTimeLocationAlert() { 2222 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2223 builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() { 2224 @Override 2225 public void onCallback(Boolean locationOn) { 2226 Keys.setLocation(mSettingsManager, locationOn, mLocationManager); 2227 } 2228 }); 2229 if (builder != null) { 2230 return builder.create(); 2231 } else { 2232 return null; 2233 } 2234 } 2235 2236 /** 2237 * Launches an ACTION_EDIT intent for the given local data item. If 2238 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2239 * the user start either the tiny planet editor or another photo edior. 2240 * 2241 * @param data The data item to edit. 2242 */ 2243 public void launchEditor(LocalData data) { 2244 Intent intent = new Intent(Intent.ACTION_EDIT) 2245 .setDataAndType(data.getUri(), data.getMimeType()) 2246 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2247 try { 2248 launchActivityByIntent(intent); 2249 } catch (ActivityNotFoundException e) { 2250 final String msgEditWith = getResources().getString(R.string.edit_with); 2251 launchActivityByIntent(Intent.createChooser(intent, msgEditWith)); 2252 } 2253 } 2254 2255 @Override 2256 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2257 super.onCreateContextMenu(menu, v, menuInfo); 2258 2259 MenuInflater inflater = getMenuInflater(); 2260 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2261 } 2262 2263 @Override 2264 public boolean onContextItemSelected(MenuItem item) { 2265 switch (item.getItemId()) { 2266 case R.id.tiny_planet_editor: 2267 mMyFilmstripBottomControlListener.onTinyPlanet(); 2268 return true; 2269 case R.id.photo_editor: 2270 mMyFilmstripBottomControlListener.onEdit(); 2271 return true; 2272 } 2273 return false; 2274 } 2275 2276 /** 2277 * Launch the tiny planet editor. 2278 * 2279 * @param data The data must be a 360 degree stereographically mapped 2280 * panoramic image. It will not be modified, instead a new item 2281 * with the result will be added to the filmstrip. 2282 */ 2283 public void launchTinyPlanetEditor(LocalData data) { 2284 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2285 Bundle bundle = new Bundle(); 2286 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString()); 2287 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 2288 fragment.setArguments(bundle); 2289 fragment.show(getFragmentManager(), "tiny_planet"); 2290 } 2291 2292 /** 2293 * Returns what UI mode (capture mode or filmstrip) we are in. 2294 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2295 */ 2296 private int currentUserInterfaceMode() { 2297 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2298 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2299 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2300 } 2301 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2302 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2303 } 2304 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2305 mode = NavigationChange.Mode.LENS_BLUR; 2306 } 2307 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2308 mode = NavigationChange.Mode.HDR_PLUS; 2309 } 2310 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2311 mode = NavigationChange.Mode.PHOTO_SPHERE; 2312 } 2313 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2314 mode = NavigationChange.Mode.PANORAMA; 2315 } 2316 if (mFilmstripVisible) { 2317 mode = NavigationChange.Mode.FILMSTRIP; 2318 } 2319 return mode; 2320 } 2321 2322 private void openModule(CameraModule module) { 2323 module.init(this, isSecureCamera(), isCaptureIntent()); 2324 module.hardResetSettings(mSettingsManager); 2325 module.resume(); 2326 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2327 NavigationChange.InteractionCause.BUTTON); 2328 updatePreviewVisibility(); 2329 } 2330 2331 private void closeModule(CameraModule module) { 2332 module.pause(); 2333 mCameraAppUI.clearModuleUI(); 2334 } 2335 2336 private void performDeletion() { 2337 if (!mPendingDeletion) { 2338 return; 2339 } 2340 hideUndoDeletionBar(false); 2341 mDataAdapter.executeDeletion(); 2342 } 2343 2344 public void showUndoDeletionBar() { 2345 if (mPendingDeletion) { 2346 performDeletion(); 2347 } 2348 Log.v(TAG, "showing undo bar"); 2349 mPendingDeletion = true; 2350 if (mUndoDeletionBar == null) { 2351 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2352 mAboveFilmstripControlLayout, true); 2353 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2354 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2355 button.setOnClickListener(new View.OnClickListener() { 2356 @Override 2357 public void onClick(View view) { 2358 mDataAdapter.undoDataRemoval(); 2359 hideUndoDeletionBar(true); 2360 } 2361 }); 2362 // Setting undo bar clickable to avoid touch events going through 2363 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2364 mUndoDeletionBar.setClickable(true); 2365 // When there is user interaction going on with the undo button, we 2366 // do not want to hide the undo bar. 2367 button.setOnTouchListener(new View.OnTouchListener() { 2368 @Override 2369 public boolean onTouch(View v, MotionEvent event) { 2370 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2371 mIsUndoingDeletion = true; 2372 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2373 mIsUndoingDeletion = false; 2374 } 2375 return false; 2376 } 2377 }); 2378 } 2379 mUndoDeletionBar.setAlpha(0f); 2380 mUndoDeletionBar.setVisibility(View.VISIBLE); 2381 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2382 } 2383 2384 private void hideUndoDeletionBar(boolean withAnimation) { 2385 Log.v(TAG, "Hiding undo deletion bar"); 2386 mPendingDeletion = false; 2387 if (mUndoDeletionBar != null) { 2388 if (withAnimation) { 2389 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2390 .setListener(new Animator.AnimatorListener() { 2391 @Override 2392 public void onAnimationStart(Animator animation) { 2393 // Do nothing. 2394 } 2395 2396 @Override 2397 public void onAnimationEnd(Animator animation) { 2398 mUndoDeletionBar.setVisibility(View.GONE); 2399 } 2400 2401 @Override 2402 public void onAnimationCancel(Animator animation) { 2403 // Do nothing. 2404 } 2405 2406 @Override 2407 public void onAnimationRepeat(Animator animation) { 2408 // Do nothing. 2409 } 2410 }).start(); 2411 } else { 2412 mUndoDeletionBar.setVisibility(View.GONE); 2413 } 2414 } 2415 } 2416 2417 @Override 2418 public void onOrientationChanged(int orientation) { 2419 // We keep the last known orientation. So if the user first orient 2420 // the camera then point the camera to floor or sky, we still have 2421 // the correct orientation. 2422 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) { 2423 return; 2424 } 2425 mLastRawOrientation = orientation; 2426 if (mCurrentModule != null) { 2427 mCurrentModule.onOrientationChanged(orientation); 2428 } 2429 } 2430 2431 /** 2432 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2433 * capture intent. 2434 * 2435 * @param enable {@code true} to enable swipe. 2436 */ 2437 public void setSwipingEnabled(boolean enable) { 2438 // TODO: Bring back the functionality. 2439 if (isCaptureIntent()) { 2440 // lockPreview(true); 2441 } else { 2442 // lockPreview(!enable); 2443 } 2444 } 2445 2446 // Accessor methods for getting latency times used in performance testing 2447 public long getFirstPreviewTime() { 2448 if (mCurrentModule instanceof PhotoModule) { 2449 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2450 if (coverHiddenTime != -1) { 2451 return coverHiddenTime - mOnCreateTime; 2452 } 2453 } 2454 return -1; 2455 } 2456 2457 public long getAutoFocusTime() { 2458 return (mCurrentModule instanceof PhotoModule) ? 2459 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2460 } 2461 2462 public long getShutterLag() { 2463 return (mCurrentModule instanceof PhotoModule) ? 2464 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2465 } 2466 2467 public long getShutterToPictureDisplayedTime() { 2468 return (mCurrentModule instanceof PhotoModule) ? 2469 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2470 } 2471 2472 public long getPictureDisplayedToJpegCallbackTime() { 2473 return (mCurrentModule instanceof PhotoModule) ? 2474 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2475 } 2476 2477 public long getJpegCallbackFinishTime() { 2478 return (mCurrentModule instanceof PhotoModule) ? 2479 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2480 } 2481 2482 public long getCaptureStartTime() { 2483 return (mCurrentModule instanceof PhotoModule) ? 2484 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2485 } 2486 2487 public boolean isRecording() { 2488 return (mCurrentModule instanceof VideoModule) ? 2489 ((VideoModule) mCurrentModule).isRecording() : false; 2490 } 2491 2492 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() { 2493 return mCameraController; 2494 } 2495 2496 // For debugging purposes only. 2497 public CameraModule getCurrentModule() { 2498 return mCurrentModule; 2499 } 2500 2501 @Override 2502 public void showTutorial(AbstractTutorialOverlay tutorial) { 2503 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2504 } 2505 2506 @Override 2507 public void showErrorAndFinish(int messageId) { 2508 CameraUtil.showErrorAndFinish(this, messageId); 2509 } 2510 2511 /** 2512 * Reads the current location recording settings and passes it on to the 2513 * location manager. 2514 */ 2515 public void syncLocationManagerSetting() { 2516 Keys.syncLocationManager(mSettingsManager, mLocationManager); 2517 } 2518 2519 private void keepScreenOnForAWhile() { 2520 if (mKeepScreenOn) { 2521 return; 2522 } 2523 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2524 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2525 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2526 } 2527 2528 private void resetScreenOn() { 2529 mKeepScreenOn = false; 2530 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2531 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2532 } 2533 2534 /** 2535 * @return {@code true} if the Gallery is launched successfully. 2536 */ 2537 private boolean startGallery() { 2538 if (mGalleryIntent == null) { 2539 return false; 2540 } 2541 try { 2542 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2543 NavigationChange.InteractionCause.BUTTON); 2544 Intent startGalleryIntent = new Intent(mGalleryIntent); 2545 int currentDataId = mFilmstripController.getCurrentId(); 2546 LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId); 2547 if (currentLocalData != null) { 2548 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri()); 2549 } 2550 launchActivityByIntent(startGalleryIntent); 2551 } catch (ActivityNotFoundException e) { 2552 Log.w(TAG, "Failed to launch gallery activity, closing"); 2553 } 2554 return false; 2555 } 2556 2557 private void setNfcBeamPushUriFromData(LocalData data) { 2558 final Uri uri = data.getUri(); 2559 if (uri != Uri.EMPTY) { 2560 mNfcPushUris[0] = uri; 2561 } else { 2562 mNfcPushUris[0] = null; 2563 } 2564 } 2565 2566 /** 2567 * Updates the visibility of the filmstrip bottom controls and action bar. 2568 */ 2569 private void updateUiByData(final int dataId) { 2570 final LocalData currentData = mDataAdapter.getLocalData(dataId); 2571 if (currentData == null) { 2572 Log.w(TAG, "Current data ID not found."); 2573 hideSessionProgress(); 2574 return; 2575 } 2576 updateActionBarMenu(currentData); 2577 2578 /* Bottom controls. */ 2579 updateBottomControlsByData(currentData); 2580 2581 if (isSecureCamera()) { 2582 // We cannot show buttons in secure camera since go to other 2583 // activities might create a security hole. 2584 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2585 return; 2586 } 2587 2588 2589 setNfcBeamPushUriFromData(currentData); 2590 2591 if (!mDataAdapter.isMetadataUpdated(dataId)) { 2592 mDataAdapter.updateMetadata(dataId); 2593 } 2594 } 2595 2596 /** 2597 * Updates the bottom controls based on the data. 2598 */ 2599 private void updateBottomControlsByData(final LocalData currentData) { 2600 2601 final CameraAppUI.BottomPanel filmstripBottomPanel = 2602 mCameraAppUI.getFilmstripBottomControls(); 2603 filmstripBottomPanel.showControls(); 2604 filmstripBottomPanel.setEditButtonVisibility( 2605 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT)); 2606 filmstripBottomPanel.setShareButtonVisibility( 2607 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE)); 2608 filmstripBottomPanel.setDeleteButtonVisibility( 2609 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE)); 2610 2611 /* Progress bar */ 2612 2613 Uri contentUri = currentData.getUri(); 2614 CaptureSessionManager sessionManager = getServices() 2615 .getCaptureSessionManager(); 2616 2617 if (sessionManager.hasErrorMessage(contentUri)) { 2618 showProcessError(sessionManager.getErrorMesage(contentUri)); 2619 } else { 2620 filmstripBottomPanel.hideProgressError(); 2621 CaptureSession session = sessionManager.getSession(contentUri); 2622 2623 if (session != null) { 2624 int sessionProgress = session.getProgress(); 2625 2626 if (sessionProgress < 0) { 2627 hideSessionProgress(); 2628 } else { 2629 CharSequence progressMessage = session.getProgressMessage(); 2630 showSessionProgress(progressMessage); 2631 updateSessionProgress(sessionProgress); 2632 } 2633 } else { 2634 hideSessionProgress(); 2635 } 2636 } 2637 2638 /* View button */ 2639 2640 // We need to add this to a separate DB. 2641 final int viewButtonVisibility; 2642 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) { 2643 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2644 } else if (RgbzMetadataLoader.hasRGBZData(currentData)) { 2645 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 2646 } else { 2647 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 2648 } 2649 2650 filmstripBottomPanel.setTinyPlanetEnabled( 2651 PanoramaMetadataLoader.isPanorama360(currentData)); 2652 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 2653 } 2654 2655 private class PeekAnimationHandler extends Handler { 2656 private class DataAndCallback { 2657 LocalData mData; 2658 com.android.camera.util.Callback<Bitmap> mCallback; 2659 2660 public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap> 2661 callback) { 2662 mData = data; 2663 mCallback = callback; 2664 } 2665 } 2666 2667 public PeekAnimationHandler(Looper looper) { 2668 super(looper); 2669 } 2670 2671 /** 2672 * Starts the animation decoding job and posts a {@code Runnable} back 2673 * when when the decoding is done. 2674 * 2675 * @param data The data item to decode the thumbnail for. 2676 * @param callback {@link com.android.camera.util.Callback} after the 2677 * decoding is done. 2678 */ 2679 public void startDecodingJob(final LocalData data, 2680 final com.android.camera.util.Callback<Bitmap> callback) { 2681 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/, 2682 new DataAndCallback(data, callback)).sendToTarget(); 2683 } 2684 2685 @Override 2686 public void handleMessage(Message msg) { 2687 final LocalData data = ((DataAndCallback) msg.obj).mData; 2688 final com.android.camera.util.Callback<Bitmap> callback = 2689 ((DataAndCallback) msg.obj).mCallback; 2690 if (data == null || callback == null) { 2691 return; 2692 } 2693 2694 final Bitmap bitmap; 2695 switch (data.getLocalDataType()) { 2696 case LocalData.LOCAL_IN_PROGRESS_DATA: 2697 byte[] jpegData = Storage.getJpegForSession(data.getUri()); 2698 if (jpegData != null) { 2699 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 2700 } else { 2701 bitmap = null; 2702 } 2703 break; 2704 2705 case LocalData.LOCAL_IMAGE: 2706 FileInputStream stream; 2707 try { 2708 stream = new FileInputStream(data.getPath()); 2709 } catch (FileNotFoundException e) { 2710 Log.e(TAG, "File not found:" + data.getPath()); 2711 return; 2712 } 2713 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(), 2714 data.getRotation(), mAboveFilmstripControlLayout.getWidth(), 2715 mAboveFilmstripControlLayout.getMeasuredHeight()); 2716 if (data.getRotation() % 180 != 0) { 2717 int dummy = dim.x; 2718 dim.x = dim.y; 2719 dim.y = dummy; 2720 } 2721 bitmap = LocalDataUtil 2722 .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(), 2723 (int) (dim.x * 0.7f), (int) (dim.y * 0.7), 2724 data.getRotation(), MAX_PEEK_BITMAP_PIXELS); 2725 break; 2726 2727 case LocalData.LOCAL_VIDEO: 2728 bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath()); 2729 break; 2730 2731 default: 2732 bitmap = null; 2733 break; 2734 } 2735 2736 if (bitmap == null) { 2737 return; 2738 } 2739 2740 mMainHandler.post(new Runnable() { 2741 @Override 2742 public void run() { 2743 callback.onCallback(bitmap); 2744 } 2745 }); 2746 } 2747 } 2748 2749 private void showDetailsDialog(int dataId) { 2750 final LocalData data = mDataAdapter.getLocalData(dataId); 2751 if (data == null) { 2752 return; 2753 } 2754 MediaDetails details = data.getMediaDetails(getAndroidContext()); 2755 if (details == null) { 2756 return; 2757 } 2758 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details); 2759 detailDialog.show(); 2760 UsageStatistics.instance().mediaInteraction( 2761 fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS, 2762 NavigationChange.InteractionCause.BUTTON, fileAgeFromDataID(dataId)); 2763 } 2764 2765 /** 2766 * Show or hide action bar items depending on current data type. 2767 */ 2768 private void updateActionBarMenu(LocalData data) { 2769 if (mActionBarMenu == null) { 2770 return; 2771 } 2772 2773 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 2774 if (detailsMenuItem == null) { 2775 return; 2776 } 2777 2778 int type = data.getLocalDataType(); 2779 boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO); 2780 detailsMenuItem.setVisible(showDetails); 2781 } 2782} 2783