CameraActivity.java revision e903d53c56302d10fffb8065164816db742c44e3
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.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) { 1363 // Capture intent. 1364 modeIndex = photoIndex; 1365 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) 1366 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() 1367 .getAction()) 1368 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1369 modeIndex = mSettingsManager.getInt( 1370 SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX); 1371 } else { 1372 // If the activity has not been started using an explicit intent, 1373 // read the module index from the last time the user changed modes 1374 modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX); 1375 if ((modeIndex == gcamIndex && 1376 !GcamHelper.hasGcamCapture()) || modeIndex < 0) { 1377 modeIndex = photoIndex; 1378 } 1379 } 1380 return modeIndex; 1381 } 1382 1383 /** 1384 * Call this whenever the mode drawer or filmstrip change the visibility 1385 * state. 1386 */ 1387 private void updatePreviewVisibility() { 1388 if (mCurrentModule == null) { 1389 return; 1390 } 1391 1392 int visibility = getPreviewVisibility(); 1393 mCameraAppUI.onPreviewVisiblityChanged(visibility); 1394 updatePreviewRendering(visibility); 1395 mCurrentModule.onPreviewVisibilityChanged(visibility); 1396 } 1397 1398 private void updatePreviewRendering(int visibility) { 1399 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1400 mCameraAppUI.pausePreviewRendering(); 1401 } else { 1402 mCameraAppUI.resumePreviewRendering(); 1403 } 1404 } 1405 1406 private int getPreviewVisibility() { 1407 if (mFilmstripCoversPreview) { 1408 return ModuleController.VISIBILITY_HIDDEN; 1409 } else if (mModeListVisible){ 1410 return ModuleController.VISIBILITY_COVERED; 1411 } else { 1412 return ModuleController.VISIBILITY_VISIBLE; 1413 } 1414 } 1415 1416 private void setRotationAnimation() { 1417 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1418 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1419 Window win = getWindow(); 1420 WindowManager.LayoutParams winParams = win.getAttributes(); 1421 winParams.rotationAnimation = rotationAnimation; 1422 win.setAttributes(winParams); 1423 } 1424 1425 @Override 1426 public void onUserInteraction() { 1427 super.onUserInteraction(); 1428 if (!isFinishing()) { 1429 keepScreenOnForAWhile(); 1430 } 1431 } 1432 1433 @Override 1434 public boolean dispatchTouchEvent(MotionEvent ev) { 1435 boolean result = super.dispatchTouchEvent(ev); 1436 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1437 // Real deletion is postponed until the next user interaction after 1438 // the gesture that triggers deletion. Until real deletion is 1439 // performed, users can click the undo button to bring back the 1440 // image that they chose to delete. 1441 if (mPendingDeletion && !mIsUndoingDeletion) { 1442 performDeletion(); 1443 } 1444 } 1445 return result; 1446 } 1447 1448 @Override 1449 public void onPause() { 1450 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE); 1451 1452 /* 1453 * Save the last module index after all secure camera and icon launches, 1454 * not just on mode switches. 1455 * 1456 * Right now we exclude capture intents from this logic, because we also 1457 * ignore the cross-Activity recovery logic in onStart for capture intents. 1458 */ 1459 if (!isCaptureIntent()) { 1460 mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, 1461 mCurrentModeIndex); 1462 } 1463 1464 mPaused = true; 1465 mPeekAnimationHandler = null; 1466 mPeekAnimationThread.quitSafely(); 1467 mPeekAnimationThread = null; 1468 1469 // Delete photos that are pending deletion 1470 performDeletion(); 1471 mCurrentModule.pause(); 1472 mOrientationManager.pause(); 1473 // Close the camera and wait for the operation done. 1474 mCameraController.closeCamera(); 1475 mPanoramaViewHelper.onPause(); 1476 1477 mLocalImagesObserver.setForegroundChangeListener(null); 1478 mLocalImagesObserver.setActivityPaused(true); 1479 mLocalVideosObserver.setActivityPaused(true); 1480 mPreloader.cancelAllLoads(); 1481 resetScreenOn(); 1482 super.onPause(); 1483 } 1484 1485 @Override 1486 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1487 if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) { 1488 mResetToPreviewOnResume = false; 1489 } else { 1490 super.onActivityResult(requestCode, resultCode, data); 1491 } 1492 } 1493 1494 @Override 1495 public void onResume() { 1496 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME); 1497 Log.v(TAG, "Build info: " + Build.DISPLAY); 1498 1499 mPaused = false; 1500 updateStorageSpaceAndHint(null); 1501 1502 mLastLayoutOrientation = getResources().getConfiguration().orientation; 1503 1504 // TODO: Handle this in OrientationManager. 1505 // Auto-rotate off 1506 if (Settings.System.getInt(getContentResolver(), 1507 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 1508 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 1509 mAutoRotateScreen = false; 1510 } else { 1511 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 1512 mAutoRotateScreen = true; 1513 } 1514 1515 String action = getIntent().getAction(); 1516 // Foreground event logging 1517 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) { 1518 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_VIDEO_CAPTURE, 1519 currentUserInterfaceMode()); 1520 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)) { 1521 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_IMAGE_CAPTURE, 1522 currentUserInterfaceMode()); 1523 } else if (MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) || 1524 INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) { 1525 // logged in onCreate() 1526 } else if (Intent.ACTION_MAIN.equals(action)) { 1527 UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_MAIN, 1528 currentUserInterfaceMode()); 1529 } else { 1530 UsageStatistics.instance().foregrounded(ForegroundSource.UNKNOWN_SOURCE, 1531 currentUserInterfaceMode()); 1532 } 1533 1534 Drawable galleryLogo; 1535 if (mSecureCamera) { 1536 mGalleryIntent = null; 1537 galleryLogo = null; 1538 } else { 1539 mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext); 1540 galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent); 1541 } 1542 if (galleryLogo == null) { 1543 try { 1544 galleryLogo = getPackageManager().getActivityLogo(getComponentName()); 1545 } catch (PackageManager.NameNotFoundException e) { 1546 Log.e(TAG, "Can't get the activity logo"); 1547 } 1548 } 1549 if (mGalleryIntent != null) { 1550 mActionBar.setDisplayUseLogoEnabled(true); 1551 } 1552 mActionBar.setLogo(galleryLogo); 1553 mOrientationManager.resume(); 1554 super.onResume(); 1555 mPeekAnimationThread = new HandlerThread("Peek animation"); 1556 mPeekAnimationThread.start(); 1557 mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper()); 1558 1559 mCurrentModule.hardResetSettings(mSettingsManager); 1560 mCurrentModule.resume(); 1561 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 1562 NavigationChange.InteractionCause.BUTTON); 1563 setSwipingEnabled(true); 1564 1565 if (!mResetToPreviewOnResume) { 1566 LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId()); 1567 if (data != null) { 1568 mDataAdapter.refresh(data.getUri()); 1569 } 1570 } 1571 // The share button might be disabled to avoid double tapping. 1572 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true); 1573 // Default is showing the preview, unless disabled by explicitly 1574 // starting an activity we want to return from to the filmstrip rather 1575 // than the preview. 1576 mResetToPreviewOnResume = true; 1577 1578 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 1579 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 1580 if (!mSecureCamera) { 1581 // If it's secure camera, requestLoad() should not be called 1582 // as it will load all the data. 1583 if (!mFilmstripVisible) { 1584 mDataAdapter.requestLoad(new Callback<Void>() { 1585 @Override 1586 public void onCallback(Void result) { 1587 fillTemporarySessions(); 1588 } 1589 }); 1590 } else { 1591 mDataAdapter.requestLoadNewPhotos(); 1592 } 1593 } 1594 } 1595 mLocalImagesObserver.setActivityPaused(false); 1596 mLocalVideosObserver.setActivityPaused(false); 1597 if (!mSecureCamera) { 1598 mLocalImagesObserver.setForegroundChangeListener( 1599 new LocalMediaObserver.ChangeListener() { 1600 @Override 1601 public void onChange() { 1602 mDataAdapter.requestLoadNewPhotos(); 1603 } 1604 }); 1605 } 1606 1607 keepScreenOnForAWhile(); 1608 1609 // Lights-out mode at all times. 1610 final View rootView = findViewById(R.id.activity_root_view); 1611 mLightsOutRunnable.run(); 1612 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( 1613 new OnSystemUiVisibilityChangeListener() { 1614 @Override 1615 public void onSystemUiVisibilityChange(int visibility) { 1616 mMainHandler.removeCallbacks(mLightsOutRunnable); 1617 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS); 1618 } 1619 }); 1620 1621 mPanoramaViewHelper.onResume(); 1622 ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager); 1623 syncLocationManagerSetting(); 1624 1625 final int previewVisibility = getPreviewVisibility(); 1626 updatePreviewRendering(previewVisibility); 1627 } 1628 1629 private void fillTemporarySessions() { 1630 if (mSecureCamera) { 1631 return; 1632 } 1633 // There might be sessions still in flight (processed by our service). 1634 // Make sure they're added to the filmstrip. 1635 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener); 1636 } 1637 1638 @Override 1639 public void onStart() { 1640 super.onStart(); 1641 mPanoramaViewHelper.onStart(); 1642 1643 /* 1644 * If we're starting after launching a different Activity (lockscreen), 1645 * we need to use the last mode used in the other Activity, and 1646 * not the old one from this Activity. 1647 * 1648 * This needs to happen before CameraAppUI.resume() in order to set the 1649 * mode cover icon to the actual last mode used. 1650 * 1651 * Right now we exclude capture intents from this logic. 1652 */ 1653 int modeIndex = getModeIndex(); 1654 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) { 1655 onModeSelected(modeIndex); 1656 } 1657 1658 if (mResetToPreviewOnResume) { 1659 mCameraAppUI.resume(); 1660 mResetToPreviewOnResume = false; 1661 } 1662 } 1663 1664 @Override 1665 protected void onStop() { 1666 mPanoramaViewHelper.onStop(); 1667 if (mFeedbackHelper != null) { 1668 mFeedbackHelper.stopFeedback(); 1669 } 1670 1671 mLocationManager.disconnect(); 1672 super.onStop(); 1673 } 1674 1675 @Override 1676 public void onDestroy() { 1677 if (mSecureCamera) { 1678 unregisterReceiver(mScreenOffReceiver); 1679 } 1680 mActionBar.removeOnMenuVisibilityListener(this); 1681 mSettingsManager.removeAllListeners(); 1682 mCameraController.removeCallbackReceiver(); 1683 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 1684 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 1685 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener); 1686 mCameraAppUI.onDestroy(); 1687 mModeListView.setVisibilityChangedListener(null); 1688 mCameraController = null; 1689 mSettingsManager = null; 1690 mCameraAppUI = null; 1691 mOrientationManager = null; 1692 mButtonManager = null; 1693 CameraManagerFactory.recycle(); 1694 super.onDestroy(); 1695 } 1696 1697 @Override 1698 public void onConfigurationChanged(Configuration config) { 1699 super.onConfigurationChanged(config); 1700 Log.v(TAG, "onConfigurationChanged"); 1701 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 1702 return; 1703 } 1704 1705 if (mLastLayoutOrientation != config.orientation) { 1706 mLastLayoutOrientation = config.orientation; 1707 mCurrentModule.onLayoutOrientationChanged( 1708 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 1709 } 1710 } 1711 1712 @Override 1713 public boolean onKeyDown(int keyCode, KeyEvent event) { 1714 if (!mFilmstripVisible) { 1715 if (mCurrentModule.onKeyDown(keyCode, event)) { 1716 return true; 1717 } 1718 // Prevent software keyboard or voice search from showing up. 1719 if (keyCode == KeyEvent.KEYCODE_SEARCH 1720 || keyCode == KeyEvent.KEYCODE_MENU) { 1721 if (event.isLongPress()) { 1722 return true; 1723 } 1724 } 1725 } 1726 1727 return super.onKeyDown(keyCode, event); 1728 } 1729 1730 @Override 1731 public boolean onKeyUp(int keyCode, KeyEvent event) { 1732 if (!mFilmstripVisible) { 1733 // If a module is in the middle of capture, it should 1734 // consume the key event. 1735 if (mCurrentModule.onKeyUp(keyCode, event)) { 1736 return true; 1737 } else if (keyCode == KeyEvent.KEYCODE_MENU 1738 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 1739 // Let the mode list view consume the event. 1740 mModeListView.onMenuPressed(); 1741 return true; 1742 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 1743 mCameraAppUI.showFilmstrip(); 1744 return true; 1745 } 1746 } else { 1747 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 1748 mFilmstripController.goToNextItem(); 1749 return true; 1750 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 1751 boolean wentToPrevious = mFilmstripController.goToPreviousItem(); 1752 if (!wentToPrevious) { 1753 // at beginning of filmstrip, hide and go back to preview 1754 mCameraAppUI.hideFilmstrip(); 1755 } 1756 return true; 1757 } 1758 } 1759 return super.onKeyUp(keyCode, event); 1760 } 1761 1762 @Override 1763 public void onBackPressed() { 1764 if (!mCameraAppUI.onBackPressed()) { 1765 if (!mCurrentModule.onBackPressed()) { 1766 super.onBackPressed(); 1767 } 1768 } 1769 } 1770 1771 @Override 1772 public boolean isAutoRotateScreen() { 1773 // TODO: Move to OrientationManager. 1774 return mAutoRotateScreen; 1775 } 1776 1777 @Override 1778 public boolean onCreateOptionsMenu(Menu menu) { 1779 MenuInflater inflater = getMenuInflater(); 1780 inflater.inflate(R.menu.filmstrip_menu, menu); 1781 mActionBarMenu = menu; 1782 return super.onCreateOptionsMenu(menu); 1783 } 1784 1785 protected long getStorageSpaceBytes() { 1786 synchronized (mStorageSpaceLock) { 1787 return mStorageSpaceBytes; 1788 } 1789 } 1790 1791 protected interface OnStorageUpdateDoneListener { 1792 public void onStorageUpdateDone(long bytes); 1793 } 1794 1795 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) { 1796 /* 1797 * We execute disk operations on a background thread in order to 1798 * free up the UI thread. Synchronizing on the lock below ensures 1799 * that when getStorageSpaceBytes is called, the main thread waits 1800 * until this method has completed. 1801 * 1802 * However, .execute() does not ensure this execution block will be 1803 * run right away (.execute() schedules this AsyncTask for sometime 1804 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 1805 * tries to execute the task in parellel with other AsyncTasks, but 1806 * there's still no guarantee). 1807 * e.g. don't call this then immediately call getStorageSpaceBytes(). 1808 * Instead, pass in an OnStorageUpdateDoneListener. 1809 */ 1810 (new AsyncTask<Void, Void, Long>() { 1811 @Override 1812 protected Long doInBackground(Void ... arg) { 1813 synchronized (mStorageSpaceLock) { 1814 mStorageSpaceBytes = Storage.getAvailableSpace(); 1815 return mStorageSpaceBytes; 1816 } 1817 } 1818 1819 @Override 1820 protected void onPostExecute(Long bytes) { 1821 updateStorageHint(bytes); 1822 if (callback != null) { 1823 callback.onStorageUpdateDone(bytes); 1824 } 1825 } 1826 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 1827 } 1828 1829 protected void updateStorageHint(long storageSpace) { 1830 String message = null; 1831 if (storageSpace == Storage.UNAVAILABLE) { 1832 message = getString(R.string.no_storage); 1833 } else if (storageSpace == Storage.PREPARING) { 1834 message = getString(R.string.preparing_sd); 1835 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 1836 message = getString(R.string.access_sd_fail); 1837 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 1838 message = getString(R.string.spaceIsLow_content); 1839 } 1840 1841 if (message != null) { 1842 Log.w(TAG, "Storage warning: " + message); 1843 if (mStorageHint == null) { 1844 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message); 1845 } else { 1846 mStorageHint.setText(message); 1847 } 1848 mStorageHint.show(); 1849 } else if (mStorageHint != null) { 1850 mStorageHint.cancel(); 1851 mStorageHint = null; 1852 } 1853 } 1854 1855 protected void setResultEx(int resultCode) { 1856 mResultCodeForTesting = resultCode; 1857 setResult(resultCode); 1858 } 1859 1860 protected void setResultEx(int resultCode, Intent data) { 1861 mResultCodeForTesting = resultCode; 1862 mResultDataForTesting = data; 1863 setResult(resultCode, data); 1864 } 1865 1866 public int getResultCode() { 1867 return mResultCodeForTesting; 1868 } 1869 1870 public Intent getResultData() { 1871 return mResultDataForTesting; 1872 } 1873 1874 public boolean isSecureCamera() { 1875 return mSecureCamera; 1876 } 1877 1878 @Override 1879 public boolean isPaused() { 1880 return mPaused; 1881 } 1882 1883 @Override 1884 public int getPreferredChildModeIndex(int modeIndex) { 1885 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 1886 boolean hdrPlusOn = mSettingsManager.isHdrPlusOn(); 1887 if (hdrPlusOn && GcamHelper.hasGcamCapture()) { 1888 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1889 } 1890 } 1891 return modeIndex; 1892 } 1893 1894 @Override 1895 public void onModeSelected(int modeIndex) { 1896 if (mCurrentModeIndex == modeIndex) { 1897 return; 1898 } 1899 1900 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 1901 // Record last used camera mode for quick switching 1902 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 1903 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 1904 mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX, 1905 modeIndex); 1906 } 1907 1908 closeModule(mCurrentModule); 1909 1910 // Select the correct module index from the mode switcher index. 1911 modeIndex = getPreferredChildModeIndex(modeIndex); 1912 setModuleFromModeIndex(modeIndex); 1913 1914 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 1915 mCameraAppUI.addShutterListener(mCurrentModule); 1916 openModule(mCurrentModule); 1917 mCurrentModule.onOrientationChanged(mLastRawOrientation); 1918 // Store the module index so we can use it the next time the Camera 1919 // starts up. 1920 mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, modeIndex); 1921 } 1922 1923 /** 1924 * Shows the settings dialog. 1925 */ 1926 @Override 1927 public void onSettingsSelected() { 1928 UsageStatistics.instance().controlUsed( 1929 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 1930 Intent intent = new Intent(this, CameraSettingsActivity.class); 1931 startActivity(intent); 1932 } 1933 1934 /** 1935 * Sets the mCurrentModuleIndex, creates a new module instance for the given 1936 * index an sets it as mCurrentModule. 1937 */ 1938 private void setModuleFromModeIndex(int modeIndex) { 1939 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 1940 if (agent == null) { 1941 return; 1942 } 1943 if (!agent.requestAppForCamera()) { 1944 mCameraController.closeCamera(); 1945 } 1946 mCurrentModeIndex = agent.getModuleId(); 1947 mCurrentModule = (CameraModule) agent.createModule(this); 1948 } 1949 1950 @Override 1951 public SettingsManager getSettingsManager() { 1952 return mSettingsManager; 1953 } 1954 1955 @Override 1956 public CameraServices getServices() { 1957 return (CameraServices) getApplication(); 1958 } 1959 1960 public List<String> getSupportedModeNames() { 1961 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 1962 List<String> supported = new ArrayList<String>(); 1963 1964 for (Integer modeIndex : indices) { 1965 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 1966 if (name != null && !name.equals("")) { 1967 supported.add(name); 1968 } 1969 } 1970 return supported; 1971 } 1972 1973 @Override 1974 public ButtonManager getButtonManager() { 1975 if (mButtonManager == null) { 1976 mButtonManager = new ButtonManager(this); 1977 } 1978 return mButtonManager; 1979 } 1980 1981 /** 1982 * Creates an AlertDialog appropriate for choosing whether to enable 1983 * location on the first run of the app. 1984 */ 1985 public AlertDialog getFirstTimeLocationAlert() { 1986 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1987 builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() { 1988 @Override 1989 public void onCallback(Boolean locationOn) { 1990 mSettingsManager.setLocation(locationOn, mLocationManager); 1991 } 1992 }); 1993 if (builder != null) { 1994 return builder.create(); 1995 } else { 1996 return null; 1997 } 1998 } 1999 2000 /** 2001 * Launches an ACTION_EDIT intent for the given local data item. If 2002 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2003 * the user start either the tiny planet editor or another photo edior. 2004 * 2005 * @param data The data item to edit. 2006 */ 2007 public void launchEditor(LocalData data) { 2008 Intent intent = new Intent(Intent.ACTION_EDIT) 2009 .setDataAndType(data.getUri(), data.getMimeType()) 2010 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2011 try { 2012 launchActivityByIntent(intent); 2013 } catch (ActivityNotFoundException e) { 2014 launchActivityByIntent(Intent.createChooser(intent, null)); 2015 } 2016 } 2017 2018 @Override 2019 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2020 super.onCreateContextMenu(menu, v, menuInfo); 2021 2022 MenuInflater inflater = getMenuInflater(); 2023 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2024 } 2025 2026 @Override 2027 public boolean onContextItemSelected(MenuItem item) { 2028 switch (item.getItemId()) { 2029 case R.id.tiny_planet_editor: 2030 mMyFilmstripBottomControlListener.onTinyPlanet(); 2031 return true; 2032 case R.id.photo_editor: 2033 mMyFilmstripBottomControlListener.onEdit(); 2034 return true; 2035 } 2036 return false; 2037 } 2038 2039 /** 2040 * Launch the tiny planet editor. 2041 * 2042 * @param data The data must be a 360 degree stereographically mapped 2043 * panoramic image. It will not be modified, instead a new item 2044 * with the result will be added to the filmstrip. 2045 */ 2046 public void launchTinyPlanetEditor(LocalData data) { 2047 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2048 Bundle bundle = new Bundle(); 2049 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString()); 2050 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle()); 2051 fragment.setArguments(bundle); 2052 fragment.show(getFragmentManager(), "tiny_planet"); 2053 } 2054 2055 /** 2056 * Returns what UI mode (capture mode or filmstrip) we are in. 2057 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2058 */ 2059 private int currentUserInterfaceMode() { 2060 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2061 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2062 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2063 } 2064 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2065 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2066 } 2067 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2068 mode = NavigationChange.Mode.LENS_BLUR; 2069 } 2070 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2071 mode = NavigationChange.Mode.HDR_PLUS; 2072 } 2073 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2074 mode = NavigationChange.Mode.PHOTO_SPHERE; 2075 } 2076 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2077 mode = NavigationChange.Mode.PANORAMA; 2078 } 2079 if (mFilmstripVisible) { 2080 mode = NavigationChange.Mode.FILMSTRIP; 2081 } 2082 return mode; 2083 } 2084 2085 private void openModule(CameraModule module) { 2086 module.init(this, isSecureCamera(), isCaptureIntent()); 2087 module.hardResetSettings(mSettingsManager); 2088 module.resume(); 2089 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2090 NavigationChange.InteractionCause.BUTTON); 2091 updatePreviewVisibility(); 2092 } 2093 2094 private void closeModule(CameraModule module) { 2095 module.pause(); 2096 mCameraAppUI.clearModuleUI(); 2097 } 2098 2099 private void performDeletion() { 2100 if (!mPendingDeletion) { 2101 return; 2102 } 2103 hideUndoDeletionBar(false); 2104 mDataAdapter.executeDeletion(); 2105 } 2106 2107 public void showUndoDeletionBar() { 2108 if (mPendingDeletion) { 2109 performDeletion(); 2110 } 2111 Log.v(TAG, "showing undo bar"); 2112 mPendingDeletion = true; 2113 if (mUndoDeletionBar == null) { 2114 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2115 mAboveFilmstripControlLayout, true); 2116 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2117 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2118 button.setOnClickListener(new View.OnClickListener() { 2119 @Override 2120 public void onClick(View view) { 2121 mDataAdapter.undoDataRemoval(); 2122 hideUndoDeletionBar(true); 2123 } 2124 }); 2125 // Setting undo bar clickable to avoid touch events going through 2126 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2127 mUndoDeletionBar.setClickable(true); 2128 // When there is user interaction going on with the undo button, we 2129 // do not want to hide the undo bar. 2130 button.setOnTouchListener(new View.OnTouchListener() { 2131 @Override 2132 public boolean onTouch(View v, MotionEvent event) { 2133 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2134 mIsUndoingDeletion = true; 2135 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2136 mIsUndoingDeletion = false; 2137 } 2138 return false; 2139 } 2140 }); 2141 } 2142 mUndoDeletionBar.setAlpha(0f); 2143 mUndoDeletionBar.setVisibility(View.VISIBLE); 2144 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2145 } 2146 2147 private void hideUndoDeletionBar(boolean withAnimation) { 2148 Log.v(TAG, "Hiding undo deletion bar"); 2149 mPendingDeletion = false; 2150 if (mUndoDeletionBar != null) { 2151 if (withAnimation) { 2152 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2153 .setListener(new Animator.AnimatorListener() { 2154 @Override 2155 public void onAnimationStart(Animator animation) { 2156 // Do nothing. 2157 } 2158 2159 @Override 2160 public void onAnimationEnd(Animator animation) { 2161 mUndoDeletionBar.setVisibility(View.GONE); 2162 } 2163 2164 @Override 2165 public void onAnimationCancel(Animator animation) { 2166 // Do nothing. 2167 } 2168 2169 @Override 2170 public void onAnimationRepeat(Animator animation) { 2171 // Do nothing. 2172 } 2173 }).start(); 2174 } else { 2175 mUndoDeletionBar.setVisibility(View.GONE); 2176 } 2177 } 2178 } 2179 2180 @Override 2181 public void onOrientationChanged(int orientation) { 2182 // We keep the last known orientation. So if the user first orient 2183 // the camera then point the camera to floor or sky, we still have 2184 // the correct orientation. 2185 if (orientation == OrientationManager.ORIENTATION_UNKNOWN) { 2186 return; 2187 } 2188 mLastRawOrientation = orientation; 2189 if (mCurrentModule != null) { 2190 mCurrentModule.onOrientationChanged(orientation); 2191 } 2192 } 2193 2194 /** 2195 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2196 * capture intent. 2197 * 2198 * @param enable {@code true} to enable swipe. 2199 */ 2200 public void setSwipingEnabled(boolean enable) { 2201 // TODO: Bring back the functionality. 2202 if (isCaptureIntent()) { 2203 // lockPreview(true); 2204 } else { 2205 // lockPreview(!enable); 2206 } 2207 } 2208 2209 // Accessor methods for getting latency times used in performance testing 2210 public long getFirstPreviewTime() { 2211 if (mCurrentModule instanceof PhotoModule) { 2212 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2213 if (coverHiddenTime != -1) { 2214 return coverHiddenTime - mOnCreateTime; 2215 } 2216 } 2217 return -1; 2218 } 2219 2220 public long getAutoFocusTime() { 2221 return (mCurrentModule instanceof PhotoModule) ? 2222 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2223 } 2224 2225 public long getShutterLag() { 2226 return (mCurrentModule instanceof PhotoModule) ? 2227 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2228 } 2229 2230 public long getShutterToPictureDisplayedTime() { 2231 return (mCurrentModule instanceof PhotoModule) ? 2232 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2233 } 2234 2235 public long getPictureDisplayedToJpegCallbackTime() { 2236 return (mCurrentModule instanceof PhotoModule) ? 2237 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2238 } 2239 2240 public long getJpegCallbackFinishTime() { 2241 return (mCurrentModule instanceof PhotoModule) ? 2242 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2243 } 2244 2245 public long getCaptureStartTime() { 2246 return (mCurrentModule instanceof PhotoModule) ? 2247 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2248 } 2249 2250 public boolean isRecording() { 2251 return (mCurrentModule instanceof VideoModule) ? 2252 ((VideoModule) mCurrentModule).isRecording() : false; 2253 } 2254 2255 public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() { 2256 return mCameraController; 2257 } 2258 2259 // For debugging purposes only. 2260 public CameraModule getCurrentModule() { 2261 return mCurrentModule; 2262 } 2263 2264 @Override 2265 public void showTutorial(AbstractTutorialOverlay tutorial) { 2266 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2267 } 2268 2269 /** 2270 * Reads the current location recording settings and passes it on to the 2271 * location manager. 2272 */ 2273 public void syncLocationManagerSetting() { 2274 mSettingsManager.syncLocationManager(mLocationManager); 2275 } 2276 2277 private void keepScreenOnForAWhile() { 2278 if (mKeepScreenOn) { 2279 return; 2280 } 2281 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2282 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2283 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2284 } 2285 2286 private void resetScreenOn() { 2287 mKeepScreenOn = false; 2288 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2289 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2290 } 2291 2292 /** 2293 * @return {@code true} if the Gallery is launched successfully. 2294 */ 2295 private boolean startGallery() { 2296 if (mGalleryIntent == null) { 2297 return false; 2298 } 2299 try { 2300 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2301 NavigationChange.InteractionCause.BUTTON); 2302 Intent startGalleryIntent = new Intent(mGalleryIntent); 2303 int currentDataId = mFilmstripController.getCurrentId(); 2304 LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId); 2305 if (currentLocalData != null) { 2306 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri()); 2307 } 2308 launchActivityByIntent(startGalleryIntent); 2309 } catch (ActivityNotFoundException e) { 2310 Log.w(TAG, "Failed to launch gallery activity, closing"); 2311 } 2312 return false; 2313 } 2314 2315 private void setNfcBeamPushUriFromData(LocalData data) { 2316 final Uri uri = data.getUri(); 2317 if (uri != Uri.EMPTY) { 2318 mNfcPushUris[0] = uri; 2319 } else { 2320 mNfcPushUris[0] = null; 2321 } 2322 } 2323 2324 /** 2325 * Updates the visibility of the filmstrip bottom controls and action bar. 2326 */ 2327 private void updateUiByData(final int dataId) { 2328 final LocalData currentData = mDataAdapter.getLocalData(dataId); 2329 if (currentData == null) { 2330 Log.w(TAG, "Current data ID not found."); 2331 hideSessionProgress(); 2332 return; 2333 } 2334 updateActionBarMenu(currentData); 2335 2336 /* Bottom controls. */ 2337 updateBottomControlsByData(currentData); 2338 2339 if (isSecureCamera()) { 2340 // We cannot show buttons in secure camera since go to other 2341 // activities might create a security hole. 2342 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2343 return; 2344 } 2345 2346 2347 setNfcBeamPushUriFromData(currentData); 2348 2349 if (!mDataAdapter.isMetadataUpdated(dataId)) { 2350 mDataAdapter.updateMetadata(dataId); 2351 } 2352 } 2353 2354 /** 2355 * Updates the bottom controls based on the data. 2356 */ 2357 private void updateBottomControlsByData(final LocalData currentData) { 2358 2359 final CameraAppUI.BottomPanel filmstripBottomPanel = 2360 mCameraAppUI.getFilmstripBottomControls(); 2361 filmstripBottomPanel.showControls(); 2362 filmstripBottomPanel.setEditButtonVisibility( 2363 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT)); 2364 filmstripBottomPanel.setShareButtonVisibility( 2365 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE)); 2366 filmstripBottomPanel.setDeleteButtonVisibility( 2367 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE)); 2368 2369 /* Progress bar */ 2370 2371 Uri contentUri = currentData.getUri(); 2372 CaptureSessionManager sessionManager = getServices() 2373 .getCaptureSessionManager(); 2374 2375 if (sessionManager.hasErrorMessage(contentUri)) { 2376 showProcessError(sessionManager.getErrorMesage(contentUri)); 2377 } else { 2378 filmstripBottomPanel.hideProgressError(); 2379 CaptureSession session = sessionManager.getSession(contentUri); 2380 2381 if (session != null) { 2382 int sessionProgress = session.getProgress(); 2383 2384 if (sessionProgress < 0) { 2385 hideSessionProgress(); 2386 } else { 2387 CharSequence progressMessage = session.getProgressMessage(); 2388 showSessionProgress(progressMessage); 2389 updateSessionProgress(sessionProgress); 2390 } 2391 } else { 2392 hideSessionProgress(); 2393 } 2394 } 2395 2396 /* View button */ 2397 2398 // We need to add this to a separate DB. 2399 final int viewButtonVisibility; 2400 if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) { 2401 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2402 } else if (RgbzMetadataLoader.hasRGBZData(currentData)) { 2403 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 2404 } else { 2405 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 2406 } 2407 2408 filmstripBottomPanel.setTinyPlanetEnabled( 2409 PanoramaMetadataLoader.isPanorama360(currentData)); 2410 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 2411 } 2412 2413 private class PeekAnimationHandler extends Handler { 2414 private class DataAndCallback { 2415 LocalData mData; 2416 com.android.camera.util.Callback<Bitmap> mCallback; 2417 2418 public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap> 2419 callback) { 2420 mData = data; 2421 mCallback = callback; 2422 } 2423 } 2424 2425 public PeekAnimationHandler(Looper looper) { 2426 super(looper); 2427 } 2428 2429 /** 2430 * Starts the animation decoding job and posts a {@code Runnable} back 2431 * when when the decoding is done. 2432 * 2433 * @param data The data item to decode the thumbnail for. 2434 * @param callback {@link com.android.camera.util.Callback} after the 2435 * decoding is done. 2436 */ 2437 public void startDecodingJob(final LocalData data, 2438 final com.android.camera.util.Callback<Bitmap> callback) { 2439 PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/, 2440 new DataAndCallback(data, callback)).sendToTarget(); 2441 } 2442 2443 @Override 2444 public void handleMessage(Message msg) { 2445 final LocalData data = ((DataAndCallback) msg.obj).mData; 2446 final com.android.camera.util.Callback<Bitmap> callback = 2447 ((DataAndCallback) msg.obj).mCallback; 2448 if (data == null || callback == null) { 2449 return; 2450 } 2451 2452 final Bitmap bitmap; 2453 switch (data.getLocalDataType()) { 2454 case LocalData.LOCAL_IN_PROGRESS_DATA: 2455 byte[] jpegData = Storage.getJpegForSession(data.getUri()); 2456 if (jpegData != null) { 2457 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 2458 } else { 2459 bitmap = null; 2460 } 2461 break; 2462 2463 case LocalData.LOCAL_IMAGE: 2464 FileInputStream stream; 2465 try { 2466 stream = new FileInputStream(data.getPath()); 2467 } catch (FileNotFoundException e) { 2468 Log.e(TAG, "File not found:" + data.getPath()); 2469 return; 2470 } 2471 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(), 2472 data.getRotation(), mAboveFilmstripControlLayout.getWidth(), 2473 mAboveFilmstripControlLayout.getMeasuredHeight()); 2474 if (data.getRotation() % 180 != 0) { 2475 int dummy = dim.x; 2476 dim.x = dim.y; 2477 dim.y = dummy; 2478 } 2479 bitmap = LocalDataUtil 2480 .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(), 2481 (int) (dim.x * 0.7f), (int) (dim.y * 0.7), 2482 data.getRotation(), MAX_PEEK_BITMAP_PIXELS); 2483 break; 2484 2485 case LocalData.LOCAL_VIDEO: 2486 bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath()); 2487 break; 2488 2489 default: 2490 bitmap = null; 2491 break; 2492 } 2493 2494 if (bitmap == null) { 2495 return; 2496 } 2497 2498 mMainHandler.post(new Runnable() { 2499 @Override 2500 public void run() { 2501 callback.onCallback(bitmap); 2502 } 2503 }); 2504 } 2505 } 2506 2507 private void showDetailsDialog(int dataId) { 2508 final LocalData data = mDataAdapter.getLocalData(dataId); 2509 if (data == null) { 2510 return; 2511 } 2512 MediaDetails details = data.getMediaDetails(getAndroidContext()); 2513 if (details == null) { 2514 return; 2515 } 2516 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details); 2517 detailDialog.show(); 2518 UsageStatistics.instance().mediaInteraction( 2519 fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS, 2520 NavigationChange.InteractionCause.BUTTON); 2521 } 2522 2523 /** 2524 * Show or hide action bar items depending on current data type. 2525 */ 2526 private void updateActionBarMenu(LocalData data) { 2527 if (mActionBarMenu == null) { 2528 return; 2529 } 2530 2531 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 2532 if (detailsMenuItem == null) { 2533 return; 2534 } 2535 2536 int type = data.getLocalDataType(); 2537 boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO); 2538 detailsMenuItem.setVisible(showDetails); 2539 } 2540} 2541