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