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