1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package com.android.camera; 19 20import android.Manifest; 21import android.animation.Animator; 22import android.app.ActionBar; 23import android.app.Activity; 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.Matrix; 36import android.graphics.RectF; 37import android.graphics.SurfaceTexture; 38import android.graphics.drawable.ColorDrawable; 39import android.graphics.drawable.Drawable; 40import android.net.Uri; 41import android.nfc.NfcAdapter; 42import android.nfc.NfcAdapter.CreateBeamUrisCallback; 43import android.nfc.NfcEvent; 44import android.os.AsyncTask; 45import android.os.Build; 46import android.os.Bundle; 47import android.os.Handler; 48import android.os.Looper; 49import android.os.Message; 50import android.provider.MediaStore; 51import android.provider.Settings; 52import android.text.TextUtils; 53import android.util.CameraPerformanceTracker; 54import android.view.ContextMenu; 55import android.view.ContextMenu.ContextMenuInfo; 56import android.view.KeyEvent; 57import android.view.Menu; 58import android.view.MenuInflater; 59import android.view.MenuItem; 60import android.view.MotionEvent; 61import android.view.View; 62import android.view.View.OnSystemUiVisibilityChangeListener; 63import android.view.ViewGroup; 64import android.view.Window; 65import android.view.WindowManager; 66import android.widget.FrameLayout; 67import android.widget.ImageView; 68import android.widget.ShareActionProvider; 69 70import com.android.camera.app.AppController; 71import com.android.camera.app.CameraAppUI; 72import com.android.camera.app.CameraController; 73import com.android.camera.app.CameraProvider; 74import com.android.camera.app.CameraServices; 75import com.android.camera.app.CameraServicesImpl; 76import com.android.camera.app.FirstRunDialog; 77import com.android.camera.app.LocationManager; 78import com.android.camera.app.MemoryManager; 79import com.android.camera.app.MemoryQuery; 80import com.android.camera.app.ModuleManager; 81import com.android.camera.app.ModuleManager.ModuleAgent; 82import com.android.camera.app.ModuleManagerImpl; 83import com.android.camera.app.MotionManager; 84import com.android.camera.app.OrientationManager; 85import com.android.camera.app.OrientationManagerImpl; 86import com.android.camera.data.CameraFilmstripDataAdapter; 87import com.android.camera.data.FilmstripContentObserver; 88import com.android.camera.data.FilmstripItem; 89import com.android.camera.data.FilmstripItemData; 90import com.android.camera.data.FilmstripItemType; 91import com.android.camera.data.FilmstripItemUtils; 92import com.android.camera.data.FixedLastProxyAdapter; 93import com.android.camera.data.GlideFilmstripManager; 94import com.android.camera.data.LocalFilmstripDataAdapter; 95import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener; 96import com.android.camera.data.MediaDetails; 97import com.android.camera.data.MetadataLoader; 98import com.android.camera.data.PhotoDataFactory; 99import com.android.camera.data.PhotoItem; 100import com.android.camera.data.PhotoItemFactory; 101import com.android.camera.data.PlaceholderItem; 102import com.android.camera.data.SessionItem; 103import com.android.camera.data.VideoDataFactory; 104import com.android.camera.data.VideoItemFactory; 105import com.android.camera.debug.Log; 106import com.android.camera.device.ActiveCameraDeviceTracker; 107import com.android.camera.device.CameraId; 108import com.android.camera.filmstrip.FilmstripContentPanel; 109import com.android.camera.filmstrip.FilmstripController; 110import com.android.camera.module.ModuleController; 111import com.android.camera.module.ModulesInfo; 112import com.android.camera.one.OneCameraException; 113import com.android.camera.one.OneCameraManager; 114import com.android.camera.one.OneCameraModule; 115import com.android.camera.one.OneCameraOpener; 116import com.android.camera.one.config.OneCameraFeatureConfig; 117import com.android.camera.one.config.OneCameraFeatureConfigCreator; 118import com.android.camera.session.CaptureSession; 119import com.android.camera.session.CaptureSessionManager; 120import com.android.camera.session.CaptureSessionManager.SessionListener; 121import com.android.camera.settings.AppUpgrader; 122import com.android.camera.settings.CameraSettingsActivity; 123import com.android.camera.settings.Keys; 124import com.android.camera.settings.PictureSizeLoader; 125import com.android.camera.settings.ResolutionSetting; 126import com.android.camera.settings.ResolutionUtil; 127import com.android.camera.settings.SettingsManager; 128import com.android.camera.stats.UsageStatistics; 129import com.android.camera.stats.profiler.Profile; 130import com.android.camera.stats.profiler.Profiler; 131import com.android.camera.stats.profiler.Profilers; 132import com.android.camera.tinyplanet.TinyPlanetFragment; 133import com.android.camera.ui.AbstractTutorialOverlay; 134import com.android.camera.ui.DetailsDialog; 135import com.android.camera.ui.MainActivityLayout; 136import com.android.camera.ui.ModeListView; 137import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener; 138import com.android.camera.ui.PreviewStatusListener; 139import com.android.camera.util.ApiHelper; 140import com.android.camera.util.Callback; 141import com.android.camera.util.CameraUtil; 142import com.android.camera.util.GalleryHelper; 143import com.android.camera.util.GcamHelper; 144import com.android.camera.util.GoogleHelpHelper; 145import com.android.camera.util.IntentHelper; 146import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; 147import com.android.camera.util.QuickActivity; 148import com.android.camera.util.ReleaseHelper; 149import com.android.camera.widget.FilmstripView; 150import com.android.camera.widget.Preloader; 151import com.android.camera2.R; 152import com.android.ex.camera2.portability.CameraAgent; 153import com.android.ex.camera2.portability.CameraAgentFactory; 154import com.android.ex.camera2.portability.CameraExceptionHandler; 155import com.android.ex.camera2.portability.CameraSettings; 156import com.bumptech.glide.Glide; 157import com.bumptech.glide.GlideBuilder; 158import com.bumptech.glide.MemoryCategory; 159import com.bumptech.glide.load.DecodeFormat; 160import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor; 161 162import com.google.common.base.Optional; 163import com.google.common.logging.eventprotos; 164import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource; 165import com.google.common.logging.eventprotos.MediaInteraction; 166import com.google.common.logging.eventprotos.NavigationChange; 167 168import java.io.File; 169import java.lang.ref.WeakReference; 170import java.util.ArrayList; 171import java.util.HashMap; 172import java.util.List; 173 174public class CameraActivity extends QuickActivity 175 implements AppController, CameraAgent.CameraOpenCallback, 176 ShareActionProvider.OnShareTargetSelectedListener { 177 178 private static final Log.Tag TAG = new Log.Tag("CameraActivity"); 179 180 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 181 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 182 public static final String ACTION_IMAGE_CAPTURE_SECURE = 183 "android.media.action.IMAGE_CAPTURE_SECURE"; 184 185 // The intent extra for camera from secure lock screen. True if the gallery 186 // should only show newly captured pictures. sSecureAlbumId does not 187 // increment. This is used when switching between camera, camcorder, and 188 // panorama. If the extra is not set, it is in the normal camera mode. 189 public static final String SECURE_CAMERA_EXTRA = "secure_camera"; 190 191 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2; 192 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins. 193 /** Load metadata for 10 items ahead of our current. */ 194 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10; 195 private static final int PERMISSIONS_ACTIVITY_REQUEST_CODE = 1; 196 private static final int PERMISSIONS_RESULT_CODE_OK = 1; 197 private static final int PERMISSIONS_RESULT_CODE_FAILED = 2; 198 199 /** Should be used wherever a context is needed. */ 200 private Context mAppContext; 201 202 /** 203 * Camera fatal error handling: 204 * 1) Present error dialog to guide users to exit the app. 205 * 2) If users hit home button, onPause should just call finish() to exit the app. 206 */ 207 private boolean mCameraFatalError = false; 208 209 /** 210 * Whether onResume should reset the view to the preview. 211 */ 212 private boolean mResetToPreviewOnResume = true; 213 214 /** 215 * This data adapter is used by FilmStripView. 216 */ 217 private VideoItemFactory mVideoItemFactory; 218 private PhotoItemFactory mPhotoItemFactory; 219 private LocalFilmstripDataAdapter mDataAdapter; 220 221 private ActiveCameraDeviceTracker mActiveCameraDeviceTracker; 222 private OneCameraOpener mOneCameraOpener; 223 private OneCameraManager mOneCameraManager; 224 private SettingsManager mSettingsManager; 225 private ResolutionSetting mResolutionSetting; 226 private ModeListView mModeListView; 227 private boolean mModeListVisible = false; 228 private int mCurrentModeIndex; 229 private CameraModule mCurrentModule; 230 private ModuleManagerImpl mModuleManager; 231 private FrameLayout mAboveFilmstripControlLayout; 232 private FilmstripController mFilmstripController; 233 private boolean mFilmstripVisible; 234 /** Whether the filmstrip fully covers the preview. */ 235 private boolean mFilmstripCoversPreview = false; 236 private int mResultCodeForTesting; 237 private Intent mResultDataForTesting; 238 private OnScreenHint mStorageHint; 239 private final Object mStorageSpaceLock = new Object(); 240 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES; 241 private boolean mAutoRotateScreen; 242 private boolean mSecureCamera; 243 private OrientationManagerImpl mOrientationManager; 244 private LocationManager mLocationManager; 245 private ButtonManager mButtonManager; 246 private Handler mMainHandler; 247 private PanoramaViewHelper mPanoramaViewHelper; 248 private ActionBar mActionBar; 249 private ViewGroup mUndoDeletionBar; 250 private boolean mIsUndoingDeletion = false; 251 private boolean mIsActivityRunning = false; 252 private FatalErrorHandler mFatalErrorHandler; 253 private boolean mHasCriticalPermissions; 254 255 private final Uri[] mNfcPushUris = new Uri[1]; 256 257 private FilmstripContentObserver mLocalImagesObserver; 258 private FilmstripContentObserver mLocalVideosObserver; 259 260 private boolean mPendingDeletion = false; 261 262 private CameraController mCameraController; 263 private boolean mPaused; 264 private CameraAppUI mCameraAppUI; 265 266 private Intent mGalleryIntent; 267 private long mOnCreateTime; 268 269 private Menu mActionBarMenu; 270 private Preloader<Integer, AsyncTask> mPreloader; 271 272 /** Can be used to play custom sounds. */ 273 private SoundPlayer mSoundPlayer; 274 275 /** Holds configuration for various OneCamera features. */ 276 private OneCameraFeatureConfig mFeatureConfig; 277 278 private static final int LIGHTS_OUT_DELAY_MS = 4000; 279 private final int BASE_SYS_UI_VISIBILITY = 280 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 281 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 282 private final Runnable mLightsOutRunnable = new Runnable() { 283 @Override 284 public void run() { 285 getWindow().getDecorView().setSystemUiVisibility( 286 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE); 287 } 288 }; 289 private MemoryManager mMemoryManager; 290 private MotionManager mMotionManager; 291 private final Profiler mProfiler = Profilers.instance().guard(); 292 293 /** First run dialog */ 294 private FirstRunDialog mFirstRunDialog; 295 296 @Override 297 public CameraAppUI getCameraAppUI() { 298 return mCameraAppUI; 299 } 300 301 @Override 302 public ModuleManager getModuleManager() { 303 return mModuleManager; 304 } 305 306 /** 307 * Close activity when secure app passes lock screen or screen turns 308 * off. 309 */ 310 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { 311 @Override 312 public void onReceive(Context context, Intent intent) { 313 finish(); 314 } 315 }; 316 317 /** 318 * Whether the screen is kept turned on. 319 */ 320 private boolean mKeepScreenOn; 321 private int mLastLayoutOrientation; 322 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener = 323 new CameraAppUI.BottomPanel.Listener() { 324 325 /** 326 * If the current photo is a photo sphere, this will launch the 327 * Photo Sphere panorama viewer. 328 */ 329 @Override 330 public void onExternalViewer() { 331 if (mPanoramaViewHelper == null) { 332 return; 333 } 334 final FilmstripItem data = getCurrentLocalData(); 335 if (data == null) { 336 Log.w(TAG, "Cannot open null data."); 337 return; 338 } 339 final Uri contentUri = data.getData().getUri(); 340 if (contentUri == Uri.EMPTY) { 341 Log.w(TAG, "Cannot open empty URL."); 342 return; 343 } 344 345 if (data.getMetadata().isUsePanoramaViewer()) { 346 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri); 347 } else if (data.getMetadata().isHasRgbzData()) { 348 mPanoramaViewHelper.showRgbz(contentUri); 349 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 350 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 351 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 352 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false); 353 mCameraAppUI.clearClingForViewer( 354 CameraAppUI.BottomPanel.VIEWER_REFOCUS); 355 } 356 } 357 } 358 359 @Override 360 public void onEdit() { 361 FilmstripItem data = getCurrentLocalData(); 362 if (data == null) { 363 Log.w(TAG, "Cannot edit null data."); 364 return; 365 } 366 final int currentDataId = getCurrentDataId(); 367 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 368 currentDataId), 369 MediaInteraction.InteractionType.EDIT, 370 NavigationChange.InteractionCause.BUTTON, 371 fileAgeFromAdapterAtIndex(currentDataId)); 372 launchEditor(data); 373 } 374 375 @Override 376 public void onTinyPlanet() { 377 FilmstripItem data = getCurrentLocalData(); 378 if (data == null) { 379 Log.w(TAG, "Cannot edit tiny planet on null data."); 380 return; 381 } 382 launchTinyPlanetEditor(data); 383 } 384 385 @Override 386 public void onDelete() { 387 final int currentDataId = getCurrentDataId(); 388 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 389 currentDataId), 390 MediaInteraction.InteractionType.DELETE, 391 NavigationChange.InteractionCause.BUTTON, 392 fileAgeFromAdapterAtIndex(currentDataId)); 393 removeItemAt(currentDataId); 394 } 395 396 @Override 397 public void onShare() { 398 final FilmstripItem data = getCurrentLocalData(); 399 if (data == null) { 400 Log.w(TAG, "Cannot share null data."); 401 return; 402 } 403 404 final int currentDataId = getCurrentDataId(); 405 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 406 currentDataId), 407 MediaInteraction.InteractionType.SHARE, 408 NavigationChange.InteractionCause.BUTTON, 409 fileAgeFromAdapterAtIndex(currentDataId)); 410 // If applicable, show release information before this item 411 // is shared. 412 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) { 413 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this, 414 new Callback<Void>() { 415 @Override 416 public void onCallback(Void result) { 417 share(data); 418 } 419 }); 420 } else { 421 share(data); 422 } 423 } 424 425 private void share(FilmstripItem data) { 426 Intent shareIntent = getShareIntentByData(data); 427 if (shareIntent != null) { 428 try { 429 launchActivityByIntent(shareIntent); 430 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false); 431 } catch (ActivityNotFoundException ex) { 432 // Nothing. 433 } 434 } 435 } 436 437 private int getCurrentDataId() { 438 return mFilmstripController.getCurrentAdapterIndex(); 439 } 440 441 private FilmstripItem getCurrentLocalData() { 442 return mDataAdapter.getItemAt(getCurrentDataId()); 443 } 444 445 /** 446 * Sets up the share intent and NFC properly according to the 447 * data. 448 * 449 * @param item The data to be shared. 450 */ 451 private Intent getShareIntentByData(final FilmstripItem item) { 452 Intent intent = null; 453 final Uri contentUri = item.getData().getUri(); 454 final String msgShareTo = getResources().getString(R.string.share_to); 455 456 if (item.getMetadata().isPanorama360() && 457 item.getData().getUri() != Uri.EMPTY) { 458 intent = new Intent(Intent.ACTION_SEND); 459 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE); 460 intent.putExtra(Intent.EXTRA_STREAM, contentUri); 461 } else if (item.getAttributes().canShare()) { 462 final String mimeType = item.getData().getMimeType(); 463 intent = getShareIntentFromType(mimeType); 464 if (intent != null) { 465 intent.putExtra(Intent.EXTRA_STREAM, contentUri); 466 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 467 } 468 intent = Intent.createChooser(intent, msgShareTo); 469 } 470 return intent; 471 } 472 473 /** 474 * Get the share intent according to the mimeType 475 * 476 * @param mimeType The mimeType of current data. 477 * @return the video/image's ShareIntent or null if mimeType is 478 * invalid. 479 */ 480 private Intent getShareIntentFromType(String mimeType) { 481 // Lazily create the intent object. 482 Intent intent = new Intent(Intent.ACTION_SEND); 483 if (mimeType.startsWith("video/")) { 484 intent.setType("video/*"); 485 } else { 486 if (mimeType.startsWith("image/")) { 487 intent.setType("image/*"); 488 } else { 489 Log.w(TAG, "unsupported mimeType " + mimeType); 490 } 491 } 492 return intent; 493 } 494 495 @Override 496 public void onProgressErrorClicked() { 497 FilmstripItem data = getCurrentLocalData(); 498 getServices().getCaptureSessionManager().removeErrorMessage( 499 data.getData().getUri()); 500 updateBottomControlsByData(data); 501 } 502 }; 503 504 @Override 505 public void onCameraOpened(CameraAgent.CameraProxy camera) { 506 Log.v(TAG, "onCameraOpened"); 507 if (mPaused) { 508 // We've paused, but just asynchronously opened the camera. Close it 509 // because we should be releasing the camera when paused to allow 510 // other apps to access it. 511 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera"); 512 mCameraController.closeCamera(false); 513 return; 514 } 515 516 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) { 517 // We shouldn't be here. Just close the camera and leave. 518 mCameraController.closeCamera(false); 519 throw new IllegalStateException("Camera opened but the module shouldn't be " + 520 "requesting"); 521 } 522 if (mCurrentModule != null) { 523 resetExposureCompensationToDefault(camera); 524 try { 525 mCurrentModule.onCameraAvailable(camera); 526 } catch (RuntimeException ex) { 527 Log.e(TAG, "Error connecting to camera", ex); 528 mFatalErrorHandler.onCameraOpenFailure(); 529 } 530 } else { 531 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable"); 532 } 533 Log.v(TAG, "invoking onChangeCamera"); 534 mCameraAppUI.onChangeCamera(); 535 } 536 537 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) { 538 // Reset the exposure compensation before handing the camera to module. 539 CameraSettings cameraSettings = camera.getSettings(); 540 cameraSettings.setExposureCompensationIndex(0); 541 camera.applySettings(cameraSettings); 542 } 543 544 @Override 545 public void onCameraDisabled(int cameraId) { 546 Log.w(TAG, "Camera disabled: " + cameraId); 547 mFatalErrorHandler.onCameraDisabledFailure(); 548 } 549 550 @Override 551 public void onDeviceOpenFailure(int cameraId, String info) { 552 Log.w(TAG, "Camera open failure: " + info); 553 mFatalErrorHandler.onCameraOpenFailure(); 554 } 555 556 @Override 557 public void onDeviceOpenedAlready(int cameraId, String info) { 558 Log.w(TAG, "Camera open already: " + cameraId + "," + info); 559 mFatalErrorHandler.onGenericCameraAccessFailure(); 560 } 561 562 @Override 563 public void onReconnectionFailure(CameraAgent mgr, String info) { 564 Log.w(TAG, "Camera reconnection failure:" + info); 565 mFatalErrorHandler.onCameraReconnectFailure(); 566 } 567 568 private static class MainHandler extends Handler { 569 final WeakReference<CameraActivity> mActivity; 570 571 public MainHandler(CameraActivity activity, Looper looper) { 572 super(looper); 573 mActivity = new WeakReference<CameraActivity>(activity); 574 } 575 576 @Override 577 public void handleMessage(Message msg) { 578 CameraActivity activity = mActivity.get(); 579 if (activity == null) { 580 return; 581 } 582 switch (msg.what) { 583 584 case MSG_CLEAR_SCREEN_ON_FLAG: { 585 if (!activity.mPaused) { 586 activity.getWindow().clearFlags( 587 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 588 } 589 break; 590 } 591 } 592 } 593 } 594 595 private String fileNameFromAdapterAtIndex(int index) { 596 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index); 597 if (filmstripItem == null) { 598 return ""; 599 } 600 601 File localFile = new File(filmstripItem.getData().getFilePath()); 602 return localFile.getName(); 603 } 604 605 private float fileAgeFromAdapterAtIndex(int index) { 606 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index); 607 if (filmstripItem == null) { 608 return 0; 609 } 610 611 File localFile = new File(filmstripItem.getData().getFilePath()); 612 return 0.001f * (System.currentTimeMillis() - localFile.lastModified()); 613 } 614 615 private final FilmstripContentPanel.Listener mFilmstripListener = 616 new FilmstripContentPanel.Listener() { 617 618 @Override 619 public void onSwipeOut() { 620 } 621 622 @Override 623 public void onSwipeOutBegin() { 624 mActionBar.hide(); 625 mCameraAppUI.hideBottomControls(); 626 mFilmstripCoversPreview = false; 627 updatePreviewVisibility(); 628 } 629 630 @Override 631 public void onFilmstripHidden() { 632 mFilmstripVisible = false; 633 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 634 NavigationChange.InteractionCause.SWIPE_RIGHT); 635 CameraActivity.this.setFilmstripUiVisibility(false); 636 // When the user hide the filmstrip (either swipe out or 637 // tap on back key) we move to the first item so next time 638 // when the user swipe in the filmstrip, the most recent 639 // one is shown. 640 mFilmstripController.goToFirstItem(); 641 } 642 643 @Override 644 public void onFilmstripShown() { 645 mFilmstripVisible = true; 646 mCameraAppUI.hideCaptureIndicator(); 647 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 648 NavigationChange.InteractionCause.SWIPE_LEFT); 649 updateUiByData(mFilmstripController.getCurrentAdapterIndex()); 650 } 651 652 @Override 653 public void onFocusedDataLongPressed(int adapterIndex) { 654 // Do nothing. 655 } 656 657 @Override 658 public void onFocusedDataPromoted(int adapterIndex) { 659 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 660 adapterIndex), 661 MediaInteraction.InteractionType.DELETE, 662 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex( 663 adapterIndex)); 664 removeItemAt(adapterIndex); 665 } 666 667 @Override 668 public void onFocusedDataDemoted(int adapterIndex) { 669 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex( 670 adapterIndex), 671 MediaInteraction.InteractionType.DELETE, 672 NavigationChange.InteractionCause.SWIPE_DOWN, 673 fileAgeFromAdapterAtIndex(adapterIndex)); 674 removeItemAt(adapterIndex); 675 } 676 677 @Override 678 public void onEnterFullScreenUiShown(int adapterIndex) { 679 if (mFilmstripVisible) { 680 CameraActivity.this.setFilmstripUiVisibility(true); 681 } 682 } 683 684 @Override 685 public void onLeaveFullScreenUiShown(int adapterIndex) { 686 // Do nothing. 687 } 688 689 @Override 690 public void onEnterFullScreenUiHidden(int adapterIndex) { 691 if (mFilmstripVisible) { 692 CameraActivity.this.setFilmstripUiVisibility(false); 693 } 694 } 695 696 @Override 697 public void onLeaveFullScreenUiHidden(int adapterIndex) { 698 // Do nothing. 699 } 700 701 @Override 702 public void onEnterFilmstrip(int adapterIndex) { 703 if (mFilmstripVisible) { 704 CameraActivity.this.setFilmstripUiVisibility(true); 705 } 706 } 707 708 @Override 709 public void onLeaveFilmstrip(int adapterIndex) { 710 // Do nothing. 711 } 712 713 @Override 714 public void onDataReloaded() { 715 if (!mFilmstripVisible) { 716 return; 717 } 718 updateUiByData(mFilmstripController.getCurrentAdapterIndex()); 719 } 720 721 @Override 722 public void onDataUpdated(int adapterIndex) { 723 if (!mFilmstripVisible) { 724 return; 725 } 726 updateUiByData(mFilmstripController.getCurrentAdapterIndex()); 727 } 728 729 @Override 730 public void onEnterZoomView(int adapterIndex) { 731 if (mFilmstripVisible) { 732 CameraActivity.this.setFilmstripUiVisibility(false); 733 } 734 } 735 736 @Override 737 public void onZoomAtIndexChanged(int adapterIndex, float zoom) { 738 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex); 739 long ageMillis = System.currentTimeMillis() 740 - filmstripItem.getData().getLastModifiedDate().getTime(); 741 742 // Do not log if items is to old or does not have a path (which is 743 // being used as a key). 744 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) || 745 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) { 746 return; 747 } 748 File localFile = new File(filmstripItem.getData().getFilePath()); 749 UsageStatistics.instance().mediaView(localFile.getName(), 750 filmstripItem.getData().getLastModifiedDate().getTime(), zoom); 751 } 752 753 @Override 754 public void onDataFocusChanged(final int prevIndex, final int newIndex) { 755 if (!mFilmstripVisible) { 756 return; 757 } 758 // TODO: This callback is UI event callback, should always 759 // happen on UI thread. Find the reason for this 760 // runOnUiThread() and fix it. 761 runOnUiThread(new Runnable() { 762 @Override 763 public void run() { 764 updateUiByData(newIndex); 765 } 766 }); 767 } 768 769 @Override 770 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) { 771 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount); 772 } 773 }; 774 775 private final FilmstripItemListener mFilmstripItemListener = 776 new FilmstripItemListener() { 777 @Override 778 public void onMetadataUpdated(List<Integer> indexes) { 779 if (mPaused) { 780 // Callback after the activity is paused. 781 return; 782 } 783 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 784 for (Integer index : indexes) { 785 if (index == currentIndex) { 786 updateUiByData(index); 787 // Currently we have only 1 data can be matched. 788 // No need to look for more, break. 789 break; 790 } 791 } 792 } 793 }; 794 795 public void gotoGallery() { 796 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP, 797 NavigationChange.InteractionCause.BUTTON); 798 799 mFilmstripController.goToNextItem(); 800 } 801 802 /** 803 * If 'visible' is false, this hides the action bar. Also maintains 804 * lights-out at all times. 805 * 806 * @param visible is false, this hides the action bar and filmstrip bottom 807 * controls. 808 */ 809 private void setFilmstripUiVisibility(boolean visible) { 810 mLightsOutRunnable.run(); 811 mCameraAppUI.getFilmstripBottomControls().setVisible(visible); 812 if (visible != mActionBar.isShowing()) { 813 if (visible) { 814 mActionBar.show(); 815 mCameraAppUI.showBottomControls(); 816 } else { 817 mActionBar.hide(); 818 mCameraAppUI.hideBottomControls(); 819 } 820 } 821 mFilmstripCoversPreview = visible; 822 updatePreviewVisibility(); 823 } 824 825 private void hideSessionProgress() { 826 mCameraAppUI.getFilmstripBottomControls().hideProgress(); 827 } 828 829 private void showSessionProgress(int messageId) { 830 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls(); 831 controls.setProgressText(messageId > 0 ? getString(messageId) : ""); 832 controls.hideControls(); 833 controls.hideProgressError(); 834 controls.showProgress(); 835 } 836 837 private void showProcessError(int messageId) { 838 mCameraAppUI.getFilmstripBottomControls().showProgressError( 839 messageId > 0 ? getString(messageId) : ""); 840 } 841 842 private void updateSessionProgress(int progress) { 843 mCameraAppUI.getFilmstripBottomControls().setProgress(progress); 844 } 845 846 private void updateSessionProgressText(int messageId) { 847 mCameraAppUI.getFilmstripBottomControls().setProgressText( 848 messageId > 0 ? getString(messageId) : ""); 849 } 850 851 private void setupNfcBeamPush() { 852 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext); 853 if (adapter == null) { 854 return; 855 } 856 857 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 858 // Disable beaming 859 adapter.setNdefPushMessage(null, CameraActivity.this); 860 return; 861 } 862 863 adapter.setBeamPushUris(null, CameraActivity.this); 864 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { 865 @Override 866 public Uri[] createBeamUris(NfcEvent event) { 867 return mNfcPushUris; 868 } 869 }, CameraActivity.this); 870 } 871 872 @Override 873 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) { 874 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 875 if (currentIndex < 0) { 876 return false; 877 } 878 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex), 879 MediaInteraction.InteractionType.SHARE, 880 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex)); 881 // TODO add intent.getComponent().getPackageName() 882 return true; 883 } 884 885 // Note: All callbacks come back on the main thread. 886 private final SessionListener mSessionListener = 887 new SessionListener() { 888 @Override 889 public void onSessionQueued(final Uri uri) { 890 Log.v(TAG, "onSessionQueued: " + uri); 891 if (!Storage.isSessionUri(uri)) { 892 return; 893 } 894 Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri); 895 if (newData.isPresent()) { 896 mDataAdapter.addOrUpdate(newData.get()); 897 } 898 } 899 900 @Override 901 public void onSessionUpdated(Uri uri) { 902 Log.v(TAG, "onSessionUpdated: " + uri); 903 mDataAdapter.refresh(uri); 904 } 905 906 @Override 907 public void onSessionDone(final Uri sessionUri) { 908 Log.v(TAG, "onSessionDone:" + sessionUri); 909 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri); 910 if (contentUri == null) { 911 mDataAdapter.refresh(sessionUri); 912 return; 913 } 914 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri); 915 916 // This can be null if e.g. a session is canceled (e.g. 917 // through discard panorama). It might be worth adding 918 // onSessionCanceled or the like this interface. 919 if (newData == null) { 920 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri); 921 return; 922 } 923 924 final int pos = mDataAdapter.findByContentUri(sessionUri); 925 if (pos == -1) { 926 // We do not have a placeholder for this image, perhaps 927 // due to the activity crashing or being killed. 928 mDataAdapter.addOrUpdate(newData); 929 } else { 930 // Make the PhotoItem aware of the session placeholder, to 931 // allow it to make a smooth transition to its content if it 932 // the session item is currently visible. 933 FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos); 934 if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE 935 && mFilmstripController.isVisible(oldSessionData)) { 936 Log.v(TAG, "session item visible, setting transition placeholder"); 937 newData.setSessionPlaceholderBitmap( 938 Storage.getPlaceholderForSession(sessionUri)); 939 } 940 mDataAdapter.updateItemAt(pos, newData); 941 } 942 } 943 944 @Override 945 public void onSessionProgress(final Uri uri, final int progress) { 946 if (progress < 0) { 947 // Do nothing, there is no task for this URI. 948 return; 949 } 950 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 951 if (currentIndex == -1) { 952 return; 953 } 954 if (uri.equals( 955 mDataAdapter.getItemAt(currentIndex).getData().getUri())) { 956 updateSessionProgress(progress); 957 } 958 } 959 960 @Override 961 public void onSessionProgressText(final Uri uri, final int messageId) { 962 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 963 if (currentIndex == -1) { 964 return; 965 } 966 if (uri.equals( 967 mDataAdapter.getItemAt(currentIndex).getData().getUri())) { 968 updateSessionProgressText(messageId); 969 } 970 } 971 972 @Override 973 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) { 974 // Don't show capture indicator in Photo Sphere. 975 final int photosphereModuleId = getApplicationContext().getResources() 976 .getInteger( 977 R.integer.camera_mode_photosphere); 978 if (mCurrentModeIndex == photosphereModuleId) { 979 return; 980 } 981 indicateCapture(indicator, rotationDegrees); 982 } 983 984 @Override 985 public void onSessionFailed(Uri uri, int failureMessageId, 986 boolean removeFromFilmstrip) { 987 Log.v(TAG, "onSessionFailed:" + uri); 988 989 int failedIndex = mDataAdapter.findByContentUri(uri); 990 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 991 992 if (currentIndex == failedIndex) { 993 updateSessionProgress(0); 994 showProcessError(failureMessageId); 995 mDataAdapter.refresh(uri); 996 } 997 if (removeFromFilmstrip) { 998 mFatalErrorHandler.onMediaStorageFailure(); 999 mDataAdapter.removeAt(failedIndex); 1000 } 1001 } 1002 1003 @Override 1004 public void onSessionCanceled(Uri uri) { 1005 Log.v(TAG, "onSessionCanceled:" + uri); 1006 int failedIndex = mDataAdapter.findByContentUri(uri); 1007 mDataAdapter.removeAt(failedIndex); 1008 } 1009 1010 @Override 1011 public void onSessionThumbnailUpdate(Bitmap bitmap) { 1012 } 1013 1014 @Override 1015 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) { 1016 } 1017 }; 1018 1019 @Override 1020 public Context getAndroidContext() { 1021 return mAppContext; 1022 } 1023 1024 @Override 1025 public OneCameraFeatureConfig getCameraFeatureConfig() { 1026 return mFeatureConfig; 1027 } 1028 1029 @Override 1030 public Dialog createDialog() { 1031 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen); 1032 } 1033 1034 @Override 1035 public void launchActivityByIntent(Intent intent) { 1036 // Starting from L, we prefer not to start edit activity within camera's task. 1037 mResetToPreviewOnResume = false; 1038 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1039 1040 startActivity(intent); 1041 } 1042 1043 @Override 1044 public int getCurrentModuleIndex() { 1045 return mCurrentModeIndex; 1046 } 1047 1048 @Override 1049 public String getModuleScope() { 1050 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex); 1051 return SettingsManager.getModuleSettingScope(agent.getScopeNamespace()); 1052 } 1053 1054 @Override 1055 public String getCameraScope() { 1056 // if an unopen camera i.e. negative ID is returned, which we've observed in 1057 // some automated scenarios, just return it as a valid separate scope 1058 // this could cause user issues, so log a stack trace noting the call path 1059 // which resulted in this scenario. 1060 1061 CameraId cameraId = mCameraController.getCurrentCameraId(); 1062 1063 if(cameraId == null) { 1064 Log.e(TAG, "Retrieving Camera Setting Scope with -1"); 1065 return SettingsManager.getCameraSettingScope("-1"); 1066 } 1067 1068 return SettingsManager.getCameraSettingScope(cameraId.getValue()); 1069 } 1070 1071 @Override 1072 public ModuleController getCurrentModuleController() { 1073 return mCurrentModule; 1074 } 1075 1076 @Override 1077 public int getQuickSwitchToModuleId(int currentModuleIndex) { 1078 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager, 1079 mAppContext); 1080 } 1081 1082 @Override 1083 public SurfaceTexture getPreviewBuffer() { 1084 // TODO: implement this 1085 return null; 1086 } 1087 1088 @Override 1089 public void onPreviewReadyToStart() { 1090 mCameraAppUI.onPreviewReadyToStart(); 1091 } 1092 1093 @Override 1094 public void onPreviewStarted() { 1095 mCameraAppUI.onPreviewStarted(); 1096 } 1097 1098 @Override 1099 public void addPreviewAreaSizeChangedListener( 1100 PreviewStatusListener.PreviewAreaChangedListener listener) { 1101 mCameraAppUI.addPreviewAreaChangedListener(listener); 1102 } 1103 1104 @Override 1105 public void removePreviewAreaSizeChangedListener( 1106 PreviewStatusListener.PreviewAreaChangedListener listener) { 1107 mCameraAppUI.removePreviewAreaChangedListener(listener); 1108 } 1109 1110 @Override 1111 public void setupOneShotPreviewListener() { 1112 mCameraController.setOneShotPreviewCallback(mMainHandler, 1113 new CameraAgent.CameraPreviewDataCallback() { 1114 @Override 1115 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) { 1116 mCurrentModule.onPreviewInitialDataReceived(); 1117 mCameraAppUI.onNewPreviewFrame(); 1118 } 1119 } 1120 ); 1121 } 1122 1123 @Override 1124 public void updatePreviewAspectRatio(float aspectRatio) { 1125 mCameraAppUI.updatePreviewAspectRatio(aspectRatio); 1126 } 1127 1128 @Override 1129 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) { 1130 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio); 1131 } 1132 1133 @Override 1134 public RectF getFullscreenRect() { 1135 return mCameraAppUI.getFullscreenRect(); 1136 } 1137 1138 @Override 1139 public void updatePreviewTransform(Matrix matrix) { 1140 mCameraAppUI.updatePreviewTransform(matrix); 1141 } 1142 1143 @Override 1144 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 1145 mCameraAppUI.setPreviewStatusListener(previewStatusListener); 1146 } 1147 1148 @Override 1149 public FrameLayout getModuleLayoutRoot() { 1150 return mCameraAppUI.getModuleRootView(); 1151 } 1152 1153 @Override 1154 public void setShutterEventsListener(ShutterEventsListener listener) { 1155 // TODO: implement this 1156 } 1157 1158 @Override 1159 public void setShutterEnabled(boolean enabled) { 1160 mCameraAppUI.setShutterButtonEnabled(enabled); 1161 } 1162 1163 @Override 1164 public boolean isShutterEnabled() { 1165 return mCameraAppUI.isShutterButtonEnabled(); 1166 } 1167 1168 @Override 1169 public void startFlashAnimation(boolean shortFlash) { 1170 mCameraAppUI.startFlashAnimation(shortFlash); 1171 } 1172 1173 @Override 1174 public void startPreCaptureAnimation() { 1175 // TODO: implement this 1176 } 1177 1178 @Override 1179 public void cancelPreCaptureAnimation() { 1180 // TODO: implement this 1181 } 1182 1183 @Override 1184 public void startPostCaptureAnimation() { 1185 // TODO: implement this 1186 } 1187 1188 @Override 1189 public void startPostCaptureAnimation(Bitmap thumbnail) { 1190 // TODO: implement this 1191 } 1192 1193 @Override 1194 public void cancelPostCaptureAnimation() { 1195 // TODO: implement this 1196 } 1197 1198 @Override 1199 public OrientationManager getOrientationManager() { 1200 return mOrientationManager; 1201 } 1202 1203 @Override 1204 public LocationManager getLocationManager() { 1205 return mLocationManager; 1206 } 1207 1208 @Override 1209 public void lockOrientation() { 1210 if (mOrientationManager != null) { 1211 mOrientationManager.lockOrientation(); 1212 } 1213 } 1214 1215 @Override 1216 public void unlockOrientation() { 1217 if (mOrientationManager != null) { 1218 mOrientationManager.unlockOrientation(); 1219 } 1220 } 1221 1222 /** 1223 * If not in filmstrip, this shows the capture indicator. 1224 */ 1225 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) { 1226 if (mFilmstripVisible) { 1227 return; 1228 } 1229 1230 // Don't show capture indicator in Photo Sphere. 1231 // TODO: Don't reach into resources to figure out the current mode. 1232 final int photosphereModuleId = getApplicationContext().getResources().getInteger( 1233 R.integer.camera_mode_photosphere); 1234 if (mCurrentModeIndex == photosphereModuleId) { 1235 return; 1236 } 1237 1238 mMainHandler.post(new Runnable() { 1239 @Override 1240 public void run() { 1241 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule 1242 .getPeekAccessibilityString()); 1243 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees); 1244 } 1245 }); 1246 } 1247 1248 @Override 1249 public void notifyNewMedia(Uri uri) { 1250 // TODO: This method is running on the main thread. Also we should get 1251 // rid of that AsyncTask. 1252 1253 updateStorageSpaceAndHint(null); 1254 ContentResolver cr = getContentResolver(); 1255 String mimeType = cr.getType(uri); 1256 FilmstripItem newData = null; 1257 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) { 1258 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri)); 1259 newData = mVideoItemFactory.queryContentUri(uri); 1260 if (newData == null) { 1261 Log.e(TAG, "Can't find video data in content resolver:" + uri); 1262 return; 1263 } 1264 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) { 1265 CameraUtil.broadcastNewPicture(mAppContext, uri); 1266 newData = mPhotoItemFactory.queryContentUri(uri); 1267 if (newData == null) { 1268 Log.e(TAG, "Can't find photo data in content resolver:" + uri); 1269 return; 1270 } 1271 } else { 1272 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri); 1273 return; 1274 } 1275 1276 // We are preloading the metadata for new video since we need the 1277 // rotation info for the thumbnail. 1278 new AsyncTask<FilmstripItem, Void, FilmstripItem>() { 1279 @Override 1280 protected FilmstripItem doInBackground(FilmstripItem... params) { 1281 FilmstripItem data = params[0]; 1282 MetadataLoader.loadMetadata(getAndroidContext(), data); 1283 return data; 1284 } 1285 1286 @Override 1287 protected void onPostExecute(final FilmstripItem data) { 1288 // TODO: Figure out why sometimes the data is aleady there. 1289 mDataAdapter.addOrUpdate(data); 1290 1291 // Legacy modules don't use CaptureSession, so we show the capture indicator when 1292 // the item was safed. 1293 if (mCurrentModule instanceof PhotoModule || 1294 mCurrentModule instanceof VideoModule) { 1295 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1296 @Override 1297 public void run() { 1298 final Optional<Bitmap> bitmap = data.generateThumbnail( 1299 mAboveFilmstripControlLayout.getWidth(), 1300 mAboveFilmstripControlLayout.getMeasuredHeight()); 1301 if (bitmap.isPresent()) { 1302 indicateCapture(bitmap.get(), 0); 1303 } 1304 } 1305 }); 1306 } 1307 } 1308 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData); 1309 } 1310 1311 @Override 1312 public void enableKeepScreenOn(boolean enabled) { 1313 if (mPaused) { 1314 return; 1315 } 1316 1317 mKeepScreenOn = enabled; 1318 if (mKeepScreenOn) { 1319 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 1320 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1321 } else { 1322 keepScreenOnForAWhile(); 1323 } 1324 } 1325 1326 @Override 1327 public CameraProvider getCameraProvider() { 1328 return mCameraController; 1329 } 1330 1331 @Override 1332 public OneCameraOpener getCameraOpener() { 1333 return mOneCameraOpener; 1334 } 1335 1336 private void removeItemAt(int index) { 1337 mDataAdapter.removeAt(index); 1338 if (mDataAdapter.getTotalNumber() > 1) { 1339 showUndoDeletionBar(); 1340 } else { 1341 // If camera preview is the only view left in filmstrip, 1342 // no need to show undo bar. 1343 mPendingDeletion = true; 1344 performDeletion(); 1345 if (mFilmstripVisible) { 1346 mCameraAppUI.getFilmstripContentPanel().animateHide(); 1347 } 1348 } 1349 } 1350 1351 @Override 1352 public boolean onOptionsItemSelected(MenuItem item) { 1353 // Handle presses on the action bar items 1354 switch (item.getItemId()) { 1355 case android.R.id.home: 1356 onBackPressed(); 1357 return true; 1358 case R.id.action_details: 1359 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex()); 1360 return true; 1361 case R.id.action_help_and_feedback: 1362 mResetToPreviewOnResume = false; 1363 new GoogleHelpHelper(this).launchGoogleHelp(); 1364 return true; 1365 default: 1366 return super.onOptionsItemSelected(item); 1367 } 1368 } 1369 1370 private boolean isCaptureIntent() { 1371 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction()) 1372 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) 1373 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { 1374 return true; 1375 } else { 1376 return false; 1377 } 1378 } 1379 1380 /** 1381 * Note: Make sure this callback is unregistered properly when the activity 1382 * is destroyed since we're otherwise leaking the Activity reference. 1383 */ 1384 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback 1385 = new CameraExceptionHandler.CameraExceptionCallback() { 1386 @Override 1387 public void onCameraError(int errorCode) { 1388 // Not a fatal error. only do Log.e(). 1389 Log.e(TAG, "Camera error callback. error=" + errorCode); 1390 } 1391 @Override 1392 public void onCameraException( 1393 RuntimeException ex, String commandHistory, int action, int state) { 1394 Log.e(TAG, "Camera Exception", ex); 1395 UsageStatistics.instance().cameraFailure( 1396 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION, 1397 commandHistory, action, state); 1398 onFatalError(); 1399 } 1400 @Override 1401 public void onDispatchThreadException(RuntimeException ex) { 1402 Log.e(TAG, "DispatchThread Exception", ex); 1403 UsageStatistics.instance().cameraFailure( 1404 eventprotos.CameraFailure.FailureReason.API_TIMEOUT, 1405 null, UsageStatistics.NONE, UsageStatistics.NONE); 1406 onFatalError(); 1407 } 1408 private void onFatalError() { 1409 if (mCameraFatalError) { 1410 return; 1411 } 1412 mCameraFatalError = true; 1413 1414 // If the activity receives exception during onPause, just exit the app. 1415 if (mPaused && !isFinishing()) { 1416 Log.e(TAG, "Fatal error during onPause, call Activity.finish()"); 1417 finish(); 1418 } else { 1419 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA); 1420 } 1421 } 1422 }; 1423 1424 @Override 1425 public void onNewIntentTasks(Intent intent) { 1426 onModeSelected(getModeIndex()); 1427 } 1428 1429 @Override 1430 public void onCreateTasks(Bundle state) { 1431 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start(); 1432 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START); 1433 mOnCreateTime = System.currentTimeMillis(); 1434 mAppContext = getApplicationContext(); 1435 mMainHandler = new MainHandler(this, getMainLooper()); 1436 mLocationManager = new LocationManager(mAppContext); 1437 mOrientationManager = new OrientationManagerImpl(this, mMainHandler); 1438 mSettingsManager = getServices().getSettingsManager(); 1439 mSoundPlayer = new SoundPlayer(mAppContext); 1440 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(), 1441 getServices().getMemoryManager()); 1442 mFatalErrorHandler = new FatalErrorHandlerImpl(this); 1443 checkPermissions(); 1444 if (!mHasCriticalPermissions) { 1445 Log.v(TAG, "onCreate: Missing critical permissions."); 1446 finish(); 1447 return; 1448 } 1449 profile.mark(); 1450 if (!Glide.isSetup()) { 1451 Context context = getAndroidContext(); 1452 Glide.setup(new GlideBuilder(context) 1453 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888) 1454 .setResizeService(new FifoPriorityThreadPoolExecutor(2))); 1455 1456 Glide glide = Glide.get(context); 1457 1458 // As a camera we will use a large amount of memory 1459 // for displaying images. 1460 glide.setMemoryCategory(MemoryCategory.HIGH); 1461 } 1462 profile.mark("Glide.setup"); 1463 1464 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance(); 1465 try { 1466 mOneCameraOpener = OneCameraModule.provideOneCameraOpener( 1467 mFeatureConfig, 1468 mAppContext, 1469 mActiveCameraDeviceTracker, 1470 ResolutionUtil.getDisplayMetrics(this)); 1471 mOneCameraManager = OneCameraModule.provideOneCameraManager(); 1472 } catch (OneCameraException e) { 1473 // Log error and continue start process while showing error dialog.. 1474 Log.e(TAG, "Creating camera manager failed.", e); 1475 mFatalErrorHandler.onGenericCameraAccessFailure(); 1476 } 1477 profile.mark("OneCameraManager.get"); 1478 1479 try { 1480 mCameraController = new CameraController(mAppContext, this, mMainHandler, 1481 CameraAgentFactory.getAndroidCameraAgent(mAppContext, 1482 CameraAgentFactory.CameraApi.API_1), 1483 CameraAgentFactory.getAndroidCameraAgent(mAppContext, 1484 CameraAgentFactory.CameraApi.AUTO), 1485 mActiveCameraDeviceTracker); 1486 mCameraController.setCameraExceptionHandler( 1487 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler)); 1488 } catch (AssertionError e) { 1489 Log.e(TAG, "Creating camera controller failed.", e); 1490 mFatalErrorHandler.onGenericCameraAccessFailure(); 1491 } 1492 1493 // TODO: Try to move all the resources allocation to happen as soon as 1494 // possible so we can call module.init() at the earliest time. 1495 mModuleManager = new ModuleManagerImpl(); 1496 1497 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig); 1498 1499 AppUpgrader appUpgrader = new AppUpgrader(this); 1500 appUpgrader.upgrade(mSettingsManager); 1501 1502 // Make sure the picture sizes are correctly cached for the current OS 1503 // version. 1504 profile.mark(); 1505 try { 1506 (new PictureSizeLoader(mAppContext)).computePictureSizes(); 1507 } catch (AssertionError e) { 1508 Log.e(TAG, "Creating camera controller failed.", e); 1509 mFatalErrorHandler.onGenericCameraAccessFailure(); 1510 } 1511 profile.mark("computePictureSizes"); 1512 Keys.setDefaults(mSettingsManager, mAppContext); 1513 1514 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager, 1515 getContentResolver()); 1516 1517 getWindow().requestFeature(Window.FEATURE_ACTION_BAR); 1518 // We suppress this flag via theme when drawing the system preview 1519 // background, but once we create activity here, reactivate to the 1520 // default value. The default is important for L, we don't want to 1521 // change app behavior, just starting background drawable layout. 1522 if (ApiHelper.isLOrHigher()) { 1523 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 1524 } 1525 1526 profile.mark(); 1527 setContentView(R.layout.activity_main); 1528 profile.mark("setContentView()"); 1529 // A window background is set in styles.xml for the system to show a 1530 // drawable background with gray color and camera icon before the 1531 // activity is created. We set the background to null here to prevent 1532 // overdraw, all views must take care of drawing backgrounds if 1533 // necessary. This call to setBackgroundDrawable must occur after 1534 // setContentView, otherwise a background may be set again from the 1535 // style. 1536 getWindow().setBackgroundDrawable(null); 1537 1538 mActionBar = getActionBar(); 1539 // set actionbar background to 100% or 50% transparent 1540 if (ApiHelper.isLOrHigher()) { 1541 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000)); 1542 } else { 1543 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000)); 1544 } 1545 1546 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); 1547 mModeListView.init(mModuleManager.getSupportedModeIndexList()); 1548 if (ApiHelper.HAS_ROTATION_ANIMATION) { 1549 setRotationAnimation(); 1550 } 1551 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() { 1552 @Override 1553 public void onVisibilityChanged(boolean visible) { 1554 mModeListVisible = visible; 1555 mCameraAppUI.setShutterButtonImportantToA11y(!visible); 1556 updatePreviewVisibility(); 1557 } 1558 }); 1559 1560 // Check if this is in the secure camera mode. 1561 Intent intent = getIntent(); 1562 String action = intent.getAction(); 1563 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action) 1564 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { 1565 mSecureCamera = true; 1566 } else { 1567 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); 1568 } 1569 1570 if (mSecureCamera) { 1571 // Change the window flags so that secure camera can show when 1572 // locked 1573 Window win = getWindow(); 1574 WindowManager.LayoutParams params = win.getAttributes(); 1575 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 1576 win.setAttributes(params); 1577 1578 // Filter for screen off so that we can finish secure camera 1579 // activity when screen is off. 1580 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF); 1581 registerReceiver(mShutdownReceiver, filter_screen_off); 1582 1583 // Filter for phone unlock so that we can finish secure camera 1584 // via this UI path: 1585 // 1. from secure lock screen, user starts secure camera 1586 // 2. user presses home button 1587 // 3. user unlocks phone 1588 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT); 1589 registerReceiver(mShutdownReceiver, filter_user_unlock); 1590 } 1591 mCameraAppUI = new CameraAppUI(this, 1592 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent()); 1593 1594 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener); 1595 1596 mAboveFilmstripControlLayout = 1597 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout); 1598 1599 // Add the session listener so we can track the session progress 1600 // updates. 1601 getServices().getCaptureSessionManager().addSessionListener(mSessionListener); 1602 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController(); 1603 mFilmstripController.setImageGap( 1604 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); 1605 profile.mark("Configure Camera UI"); 1606 1607 mPanoramaViewHelper = new PanoramaViewHelper(this); 1608 mPanoramaViewHelper.onCreate(); 1609 1610 ContentResolver appContentResolver = mAppContext.getContentResolver(); 1611 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext); 1612 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver, 1613 new PhotoDataFactory()); 1614 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver, 1615 new VideoDataFactory()); 1616 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener); 1617 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1618 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) { 1619 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS); 1620 } 1621 1622 setModuleFromModeIndex(getModeIndex()); 1623 1624 profile.mark(); 1625 mCameraAppUI.prepareModuleUI(); 1626 profile.mark("Init Current Module UI"); 1627 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent()); 1628 profile.mark("Init CurrentModule"); 1629 1630 preloadFilmstripItems(); 1631 1632 setupNfcBeamPush(); 1633 1634 mLocalImagesObserver = new FilmstripContentObserver(); 1635 mLocalVideosObserver = new FilmstripContentObserver(); 1636 1637 getContentResolver().registerContentObserver( 1638 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, 1639 mLocalImagesObserver); 1640 getContentResolver().registerContentObserver( 1641 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, 1642 mLocalVideosObserver); 1643 1644 mMemoryManager = getServices().getMemoryManager(); 1645 1646 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1647 @Override 1648 public void run() { 1649 HashMap memoryData = mMemoryManager.queryMemory(); 1650 UsageStatistics.instance().reportMemoryConsumed(memoryData, 1651 MemoryQuery.REPORT_LABEL_LAUNCH); 1652 } 1653 }); 1654 1655 mMotionManager = getServices().getMotionManager(); 1656 1657 mFirstRunDialog = new FirstRunDialog(this, 1658 getAndroidContext(), 1659 mResolutionSetting, 1660 mSettingsManager, 1661 mOneCameraManager, 1662 new FirstRunDialog.FirstRunDialogListener() { 1663 @Override 1664 public void onFirstRunStateReady() { 1665 // Run normal resume tasks. 1666 resume(); 1667 } 1668 1669 @Override 1670 public void onFirstRunDialogCancelled() { 1671 // App isn't functional until users finish first run dialog. 1672 // We need to finish here since users hit back button during 1673 // first run dialog (b/19593942). 1674 finish(); 1675 } 1676 1677 @Override 1678 public void onCameraAccessException() { 1679 mFatalErrorHandler.onGenericCameraAccessFailure(); 1680 } 1681 }); 1682 profile.stop(); 1683 } 1684 1685 /** 1686 * Get the current mode index from the Intent or from persistent 1687 * settings. 1688 */ 1689 private int getModeIndex() { 1690 int modeIndex = -1; 1691 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo); 1692 int videoIndex = getResources().getInteger(R.integer.camera_mode_video); 1693 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam); 1694 int captureIntentIndex = 1695 getResources().getInteger(R.integer.camera_mode_capture_intent); 1696 String intentAction = getIntent().getAction(); 1697 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction) 1698 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) { 1699 modeIndex = videoIndex; 1700 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction) 1701 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) { 1702 // Capture intent. 1703 modeIndex = captureIntentIndex; 1704 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction) 1705 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction) 1706 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) { 1707 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1708 Keys.KEY_CAMERA_MODULE_LAST_USED); 1709 1710 // For upgraders who have not seen the aspect ratio selection screen, 1711 // we need to drop them back in the photo module and have them select 1712 // aspect ratio. 1713 // TODO: Move this to SettingsManager as an upgrade procedure. 1714 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 1715 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { 1716 modeIndex = photoIndex; 1717 } 1718 } else { 1719 // If the activity has not been started using an explicit intent, 1720 // read the module index from the last time the user changed modes 1721 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL, 1722 Keys.KEY_STARTUP_MODULE_INDEX); 1723 if ((modeIndex == gcamIndex && 1724 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) { 1725 modeIndex = photoIndex; 1726 } 1727 } 1728 return modeIndex; 1729 } 1730 1731 /** 1732 * Call this whenever the mode drawer or filmstrip change the visibility 1733 * state. 1734 */ 1735 private void updatePreviewVisibility() { 1736 if (mCurrentModule == null) { 1737 return; 1738 } 1739 1740 int visibility = getPreviewVisibility(); 1741 mCameraAppUI.onPreviewVisiblityChanged(visibility); 1742 updatePreviewRendering(visibility); 1743 mCurrentModule.onPreviewVisibilityChanged(visibility); 1744 } 1745 1746 private void updatePreviewRendering(int visibility) { 1747 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1748 mCameraAppUI.pausePreviewRendering(); 1749 } else { 1750 mCameraAppUI.resumePreviewRendering(); 1751 } 1752 } 1753 1754 private int getPreviewVisibility() { 1755 if (mFilmstripCoversPreview) { 1756 return ModuleController.VISIBILITY_HIDDEN; 1757 } else if (mModeListVisible){ 1758 return ModuleController.VISIBILITY_COVERED; 1759 } else { 1760 return ModuleController.VISIBILITY_VISIBLE; 1761 } 1762 } 1763 1764 private void setRotationAnimation() { 1765 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 1766 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; 1767 Window win = getWindow(); 1768 WindowManager.LayoutParams winParams = win.getAttributes(); 1769 winParams.rotationAnimation = rotationAnimation; 1770 win.setAttributes(winParams); 1771 } 1772 1773 @Override 1774 public void onUserInteraction() { 1775 super.onUserInteraction(); 1776 if (!isFinishing()) { 1777 keepScreenOnForAWhile(); 1778 } 1779 } 1780 1781 @Override 1782 public boolean dispatchTouchEvent(MotionEvent ev) { 1783 boolean result = super.dispatchTouchEvent(ev); 1784 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1785 // Real deletion is postponed until the next user interaction after 1786 // the gesture that triggers deletion. Until real deletion is 1787 // performed, users can click the undo button to bring back the 1788 // image that they chose to delete. 1789 if (mPendingDeletion && !mIsUndoingDeletion) { 1790 performDeletion(); 1791 } 1792 } 1793 return result; 1794 } 1795 1796 @Override 1797 public void onPauseTasks() { 1798 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE); 1799 Profile profile = mProfiler.create("CameraActivity.onPause").start(); 1800 1801 /* 1802 * Save the last module index after all secure camera and icon launches, 1803 * not just on mode switches. 1804 * 1805 * Right now we exclude capture intents from this logic, because we also 1806 * ignore the cross-Activity recovery logic in onStart for capture intents. 1807 */ 1808 if (!isCaptureIntent()) { 1809 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 1810 Keys.KEY_STARTUP_MODULE_INDEX, 1811 mCurrentModeIndex); 1812 } 1813 1814 mPaused = true; 1815 mCameraAppUI.hideCaptureIndicator(); 1816 mFirstRunDialog.dismiss(); 1817 1818 // Delete photos that are pending deletion 1819 performDeletion(); 1820 mCurrentModule.pause(); 1821 mOrientationManager.pause(); 1822 mPanoramaViewHelper.onPause(); 1823 1824 mLocalImagesObserver.setForegroundChangeListener(null); 1825 mLocalImagesObserver.setActivityPaused(true); 1826 mLocalVideosObserver.setActivityPaused(true); 1827 if (mPreloader != null) { 1828 mPreloader.cancelAllLoads(); 1829 } 1830 resetScreenOn(); 1831 1832 mMotionManager.stop(); 1833 1834 // Always stop recording location when paused. Resume will start 1835 // location recording again if the location setting is on. 1836 mLocationManager.recordLocation(false); 1837 1838 UsageStatistics.instance().backgrounded(); 1839 1840 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home 1841 // button. Let's just kill the process. 1842 if (mCameraFatalError && !isFinishing()) { 1843 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()"); 1844 finish(); 1845 } else { 1846 // Close the camera and wait for the operation done. 1847 Log.v(TAG, "onPause closing camera"); 1848 if (mCameraController != null) { 1849 mCameraController.closeCamera(true); 1850 } 1851 } 1852 1853 profile.stop(); 1854 } 1855 1856 @Override 1857 public void onResumeTasks() { 1858 mPaused = false; 1859 checkPermissions(); 1860 if (!mHasCriticalPermissions) { 1861 Log.v(TAG, "onResume: Missing critical permissions."); 1862 finish(); 1863 return; 1864 } 1865 if (!mSecureCamera) { 1866 // Show the dialog if necessary. The rest resume logic will be invoked 1867 // at the onFirstRunStateReady() callback. 1868 try { 1869 mFirstRunDialog.showIfNecessary(); 1870 } catch (AssertionError e) { 1871 Log.e(TAG, "Creating camera controller failed.", e); 1872 mFatalErrorHandler.onGenericCameraAccessFailure(); 1873 } 1874 } else { 1875 // In secure mode from lockscreen, we go straight to camera and will 1876 // show first run dialog next time user enters launcher. 1877 Log.v(TAG, "in secure mode, skipping first run dialog check"); 1878 resume(); 1879 } 1880 } 1881 1882 /** 1883 * Checks if any of the needed Android runtime permissions are missing. 1884 * If they are, then launch the permissions activity under one of the following conditions: 1885 * a) The permissions dialogs have not run yet. We will ask for permission only once. 1886 * b) If the missing permissions are critical to the app running, we will display a fatal error dialog. 1887 * Critical permissions are: camera, microphone and storage. The app cannot run without them. 1888 * Non-critical permission is location. 1889 */ 1890 private void checkPermissions() { 1891 if (!ApiHelper.isMOrHigher()) { 1892 Log.v(TAG, "not running on M, skipping permission checks"); 1893 mHasCriticalPermissions = true; 1894 return; 1895 } 1896 1897 if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && 1898 checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED && 1899 checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { 1900 mHasCriticalPermissions = true; 1901 } else { 1902 mHasCriticalPermissions = false; 1903 } 1904 1905 if ((checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && 1906 !mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS)) || 1907 !mHasCriticalPermissions) { 1908 Intent intent = new Intent(this, PermissionsActivity.class); 1909 startActivity(intent); 1910 finish(); 1911 } 1912 } 1913 1914 private void preloadFilmstripItems() { 1915 if (mDataAdapter == null) { 1916 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext, 1917 mPhotoItemFactory, mVideoItemFactory); 1918 mDataAdapter.setLocalDataListener(mFilmstripItemListener); 1919 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter, 1920 mDataAdapter); 1921 if (!mSecureCamera) { 1922 mFilmstripController.setDataAdapter(mDataAdapter); 1923 if (!isCaptureIntent()) { 1924 mDataAdapter.requestLoad(new Callback<Void>() { 1925 @Override 1926 public void onCallback(Void result) { 1927 fillTemporarySessions(); 1928 } 1929 }); 1930 } 1931 } else { 1932 // Put a lock placeholder as the last image by setting its date to 1933 // 0. 1934 ImageView v = (ImageView) getLayoutInflater().inflate( 1935 R.layout.secure_album_placeholder, null); 1936 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal()); 1937 v.setOnClickListener(new View.OnClickListener() { 1938 @Override 1939 public void onClick(View view) { 1940 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 1941 NavigationChange.InteractionCause.BUTTON); 1942 startGallery(); 1943 finish(); 1944 } 1945 }); 1946 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera)); 1947 mDataAdapter = new FixedLastProxyAdapter( 1948 mAppContext, 1949 mDataAdapter, 1950 new PlaceholderItem( 1951 v, 1952 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER, 1953 v.getDrawable().getIntrinsicWidth(), 1954 v.getDrawable().getIntrinsicHeight())); 1955 // Flush out all the original data. 1956 mDataAdapter.clear(); 1957 mFilmstripController.setDataAdapter(mDataAdapter); 1958 } 1959 } 1960 } 1961 1962 private void resume() { 1963 Profile profile = mProfiler.create("CameraActivity.resume").start(); 1964 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME); 1965 Log.v(TAG, "Build info: " + Build.DISPLAY); 1966 updateStorageSpaceAndHint(null); 1967 1968 mLastLayoutOrientation = getResources().getConfiguration().orientation; 1969 1970 // TODO: Handle this in OrientationManager. 1971 // Auto-rotate off 1972 if (Settings.System.getInt(getContentResolver(), 1973 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) { 1974 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 1975 mAutoRotateScreen = false; 1976 } else { 1977 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 1978 mAutoRotateScreen = true; 1979 } 1980 1981 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and 1982 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to 1983 // lockscreen onResume->onPause->onResume sequence. 1984 int source; 1985 String action = getIntent().getAction(); 1986 if (action == null) { 1987 source = ForegroundSource.UNKNOWN_SOURCE; 1988 } else { 1989 switch (action) { 1990 case MediaStore.ACTION_IMAGE_CAPTURE: 1991 source = ForegroundSource.ACTION_IMAGE_CAPTURE; 1992 break; 1993 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA: 1994 // was UNKNOWN_SOURCE in Fishlake. 1995 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA; 1996 break; 1997 case MediaStore.INTENT_ACTION_VIDEO_CAMERA: 1998 // was UNKNOWN_SOURCE in Fishlake. 1999 source = ForegroundSource.ACTION_VIDEO_CAMERA; 2000 break; 2001 case MediaStore.ACTION_VIDEO_CAPTURE: 2002 source = ForegroundSource.ACTION_VIDEO_CAPTURE; 2003 break; 2004 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE: 2005 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake. 2006 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE; 2007 break; 2008 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE: 2009 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE; 2010 break; 2011 case Intent.ACTION_MAIN: 2012 source = ForegroundSource.ACTION_MAIN; 2013 break; 2014 default: 2015 source = ForegroundSource.UNKNOWN_SOURCE; 2016 break; 2017 } 2018 } 2019 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(), 2020 isKeyguardSecure(), isKeyguardLocked(), 2021 mStartupOnCreate, mExecutionStartNanoTime); 2022 2023 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext); 2024 if (ApiHelper.isLOrHigher()) { 2025 // hide the up affordance for L devices, it's not very Materially 2026 mActionBar.setDisplayShowHomeEnabled(false); 2027 } 2028 2029 mOrientationManager.resume(); 2030 2031 mCurrentModule.hardResetSettings(mSettingsManager); 2032 2033 profile.mark(); 2034 mCurrentModule.resume(); 2035 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2036 NavigationChange.InteractionCause.BUTTON); 2037 setSwipingEnabled(true); 2038 profile.mark("mCurrentModule.resume"); 2039 2040 if (!mResetToPreviewOnResume) { 2041 FilmstripItem item = mDataAdapter.getItemAt( 2042 mFilmstripController.getCurrentAdapterIndex()); 2043 if (item != null) { 2044 mDataAdapter.refresh(item.getData().getUri()); 2045 } 2046 } 2047 2048 // The share button might be disabled to avoid double tapping. 2049 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true); 2050 // Default is showing the preview, unless disabled by explicitly 2051 // starting an activity we want to return from to the filmstrip rather 2052 // than the preview. 2053 mResetToPreviewOnResume = true; 2054 2055 if (mLocalVideosObserver.isMediaDataChangedDuringPause() 2056 || mLocalImagesObserver.isMediaDataChangedDuringPause()) { 2057 if (!mSecureCamera) { 2058 // If it's secure camera, requestLoad() should not be called 2059 // as it will load all the data. 2060 if (!mFilmstripVisible) { 2061 mDataAdapter.requestLoad(new Callback<Void>() { 2062 @Override 2063 public void onCallback(Void result) { 2064 fillTemporarySessions(); 2065 } 2066 }); 2067 } else { 2068 mDataAdapter.requestLoadNewPhotos(); 2069 } 2070 } 2071 } 2072 mLocalImagesObserver.setActivityPaused(false); 2073 mLocalVideosObserver.setActivityPaused(false); 2074 if (!mSecureCamera) { 2075 mLocalImagesObserver.setForegroundChangeListener( 2076 new FilmstripContentObserver.ChangeListener() { 2077 @Override 2078 public void onChange() { 2079 mDataAdapter.requestLoadNewPhotos(); 2080 } 2081 }); 2082 } 2083 2084 keepScreenOnForAWhile(); 2085 2086 // Lights-out mode at all times. 2087 final View rootView = findViewById(R.id.activity_root_view); 2088 mLightsOutRunnable.run(); 2089 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( 2090 new OnSystemUiVisibilityChangeListener() { 2091 @Override 2092 public void onSystemUiVisibilityChange(int visibility) { 2093 mMainHandler.removeCallbacks(mLightsOutRunnable); 2094 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS); 2095 } 2096 }); 2097 2098 profile.mark(); 2099 mPanoramaViewHelper.onResume(); 2100 profile.mark("mPanoramaViewHelper.onResume()"); 2101 2102 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager); 2103 // Enable location recording if the setting is on. 2104 final boolean locationRecordingEnabled = 2105 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION); 2106 mLocationManager.recordLocation(locationRecordingEnabled); 2107 2108 final int previewVisibility = getPreviewVisibility(); 2109 updatePreviewRendering(previewVisibility); 2110 2111 mMotionManager.start(); 2112 profile.stop(); 2113 } 2114 2115 private void fillTemporarySessions() { 2116 if (mSecureCamera) { 2117 return; 2118 } 2119 // There might be sessions still in flight (processed by our service). 2120 // Make sure they're added to the filmstrip. 2121 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener); 2122 } 2123 2124 @Override 2125 public void onStartTasks() { 2126 mIsActivityRunning = true; 2127 mPanoramaViewHelper.onStart(); 2128 2129 /* 2130 * If we're starting after launching a different Activity (lockscreen), 2131 * we need to use the last mode used in the other Activity, and 2132 * not the old one from this Activity. 2133 * 2134 * This needs to happen before CameraAppUI.resume() in order to set the 2135 * mode cover icon to the actual last mode used. 2136 * 2137 * Right now we exclude capture intents from this logic. 2138 */ 2139 int modeIndex = getModeIndex(); 2140 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) { 2141 onModeSelected(modeIndex); 2142 } 2143 2144 if (mResetToPreviewOnResume) { 2145 mCameraAppUI.resume(); 2146 mResetToPreviewOnResume = false; 2147 } 2148 } 2149 2150 @Override 2151 protected void onStopTasks() { 2152 mIsActivityRunning = false; 2153 mPanoramaViewHelper.onStop(); 2154 2155 mLocationManager.disconnect(); 2156 } 2157 2158 @Override 2159 public void onDestroyTasks() { 2160 if (mSecureCamera) { 2161 unregisterReceiver(mShutdownReceiver); 2162 } 2163 2164 // Ensure anything that checks for "isPaused" returns true. 2165 mPaused = true; 2166 2167 mSettingsManager.removeAllListeners(); 2168 if (mCameraController != null) { 2169 mCameraController.removeCallbackReceiver(); 2170 mCameraController.setCameraExceptionHandler(null); 2171 } 2172 if (mLocalImagesObserver != null) { 2173 getContentResolver().unregisterContentObserver(mLocalImagesObserver); 2174 } 2175 if (mLocalVideosObserver != null) { 2176 getContentResolver().unregisterContentObserver(mLocalVideosObserver); 2177 } 2178 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener); 2179 if (mCameraAppUI != null) { 2180 mCameraAppUI.onDestroy(); 2181 } 2182 if (mModeListView != null) { 2183 mModeListView.setVisibilityChangedListener(null); 2184 } 2185 mCameraController = null; 2186 mSettingsManager = null; 2187 mOrientationManager = null; 2188 mButtonManager = null; 2189 if (mSoundPlayer != null) { 2190 mSoundPlayer.release(); 2191 } 2192 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1); 2193 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO); 2194 } 2195 2196 @Override 2197 public void onConfigurationChanged(Configuration config) { 2198 super.onConfigurationChanged(config); 2199 Log.v(TAG, "onConfigurationChanged"); 2200 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) { 2201 return; 2202 } 2203 2204 if (mLastLayoutOrientation != config.orientation) { 2205 mLastLayoutOrientation = config.orientation; 2206 mCurrentModule.onLayoutOrientationChanged( 2207 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE); 2208 } 2209 } 2210 2211 @Override 2212 public boolean onKeyDown(int keyCode, KeyEvent event) { 2213 if (!mFilmstripVisible) { 2214 if (mCurrentModule.onKeyDown(keyCode, event)) { 2215 return true; 2216 } 2217 // Prevent software keyboard or voice search from showing up. 2218 if (keyCode == KeyEvent.KEYCODE_SEARCH 2219 || keyCode == KeyEvent.KEYCODE_MENU) { 2220 if (event.isLongPress()) { 2221 return true; 2222 } 2223 } 2224 } 2225 2226 return super.onKeyDown(keyCode, event); 2227 } 2228 2229 @Override 2230 public boolean onKeyUp(int keyCode, KeyEvent event) { 2231 if (!mFilmstripVisible) { 2232 // If a module is in the middle of capture, it should 2233 // consume the key event. 2234 if (mCurrentModule.onKeyUp(keyCode, event)) { 2235 return true; 2236 } else if (keyCode == KeyEvent.KEYCODE_MENU 2237 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 2238 // Let the mode list view consume the event. 2239 mCameraAppUI.openModeList(); 2240 return true; 2241 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 2242 mCameraAppUI.showFilmstrip(); 2243 return true; 2244 } 2245 } else { 2246 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 2247 mFilmstripController.goToNextItem(); 2248 return true; 2249 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { 2250 boolean wentToPrevious = mFilmstripController.goToPreviousItem(); 2251 if (!wentToPrevious) { 2252 // at beginning of filmstrip, hide and go back to preview 2253 mCameraAppUI.hideFilmstrip(); 2254 } 2255 return true; 2256 } 2257 } 2258 return super.onKeyUp(keyCode, event); 2259 } 2260 2261 @Override 2262 public void onBackPressed() { 2263 if (!mCameraAppUI.onBackPressed()) { 2264 if (!mCurrentModule.onBackPressed()) { 2265 super.onBackPressed(); 2266 } 2267 } 2268 } 2269 2270 @Override 2271 public boolean isAutoRotateScreen() { 2272 // TODO: Move to OrientationManager. 2273 return mAutoRotateScreen; 2274 } 2275 2276 @Override 2277 public boolean onCreateOptionsMenu(Menu menu) { 2278 MenuInflater inflater = getMenuInflater(); 2279 inflater.inflate(R.menu.filmstrip_menu, menu); 2280 mActionBarMenu = menu; 2281 2282 // add a button for launching the gallery 2283 if (mGalleryIntent != null) { 2284 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent); 2285 if (appName != null) { 2286 MenuItem menuItem = menu.add(appName); 2287 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 2288 menuItem.setIntent(mGalleryIntent); 2289 2290 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent); 2291 if (galleryLogo != null) { 2292 menuItem.setIcon(galleryLogo); 2293 } 2294 } 2295 } 2296 2297 return super.onCreateOptionsMenu(menu); 2298 } 2299 2300 @Override 2301 public boolean onPrepareOptionsMenu(Menu menu) { 2302 if (isSecureCamera() && !ApiHelper.isLOrHigher()) { 2303 // Compatibility pre-L: launching new activities right above 2304 // lockscreen does not reliably work, only show help if not secure 2305 menu.removeItem(R.id.action_help_and_feedback); 2306 } 2307 2308 return super.onPrepareOptionsMenu(menu); 2309 } 2310 2311 protected long getStorageSpaceBytes() { 2312 synchronized (mStorageSpaceLock) { 2313 return mStorageSpaceBytes; 2314 } 2315 } 2316 2317 protected interface OnStorageUpdateDoneListener { 2318 public void onStorageUpdateDone(long bytes); 2319 } 2320 2321 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) { 2322 /* 2323 * We execute disk operations on a background thread in order to 2324 * free up the UI thread. Synchronizing on the lock below ensures 2325 * that when getStorageSpaceBytes is called, the main thread waits 2326 * until this method has completed. 2327 * 2328 * However, .execute() does not ensure this execution block will be 2329 * run right away (.execute() schedules this AsyncTask for sometime 2330 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 2331 * tries to execute the task in parellel with other AsyncTasks, but 2332 * there's still no guarantee). 2333 * e.g. don't call this then immediately call getStorageSpaceBytes(). 2334 * Instead, pass in an OnStorageUpdateDoneListener. 2335 */ 2336 (new AsyncTask<Void, Void, Long>() { 2337 @Override 2338 protected Long doInBackground(Void ... arg) { 2339 synchronized (mStorageSpaceLock) { 2340 mStorageSpaceBytes = Storage.getAvailableSpace(); 2341 return mStorageSpaceBytes; 2342 } 2343 } 2344 2345 @Override 2346 protected void onPostExecute(Long bytes) { 2347 updateStorageHint(bytes); 2348 // This callback returns after I/O to check disk, so we could be 2349 // pausing and shutting down. If so, don't bother invoking. 2350 if (callback != null && !mPaused) { 2351 callback.onStorageUpdateDone(bytes); 2352 } else { 2353 Log.v(TAG, "ignoring storage callback after activity pause"); 2354 } 2355 } 2356 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2357 } 2358 2359 protected void updateStorageHint(long storageSpace) { 2360 if (!mIsActivityRunning) { 2361 return; 2362 } 2363 2364 String message = null; 2365 if (storageSpace == Storage.UNAVAILABLE) { 2366 message = getString(R.string.no_storage); 2367 } else if (storageSpace == Storage.PREPARING) { 2368 message = getString(R.string.preparing_sd); 2369 } else if (storageSpace == Storage.UNKNOWN_SIZE) { 2370 message = getString(R.string.access_sd_fail); 2371 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 2372 message = getString(R.string.spaceIsLow_content); 2373 } 2374 2375 if (message != null) { 2376 Log.w(TAG, "Storage warning: " + message); 2377 if (mStorageHint == null) { 2378 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message); 2379 } else { 2380 mStorageHint.setText(message); 2381 } 2382 mStorageHint.show(); 2383 UsageStatistics.instance().storageWarning(storageSpace); 2384 2385 // Disable all user interactions, 2386 mCameraAppUI.setDisableAllUserInteractions(true); 2387 } else if (mStorageHint != null) { 2388 mStorageHint.cancel(); 2389 mStorageHint = null; 2390 2391 // Re-enable all user interactions. 2392 mCameraAppUI.setDisableAllUserInteractions(false); 2393 } 2394 } 2395 2396 protected void setResultEx(int resultCode) { 2397 mResultCodeForTesting = resultCode; 2398 setResult(resultCode); 2399 } 2400 2401 protected void setResultEx(int resultCode, Intent data) { 2402 mResultCodeForTesting = resultCode; 2403 mResultDataForTesting = data; 2404 setResult(resultCode, data); 2405 } 2406 2407 public int getResultCode() { 2408 return mResultCodeForTesting; 2409 } 2410 2411 public Intent getResultData() { 2412 return mResultDataForTesting; 2413 } 2414 2415 public boolean isSecureCamera() { 2416 return mSecureCamera; 2417 } 2418 2419 @Override 2420 public boolean isPaused() { 2421 return mPaused; 2422 } 2423 2424 @Override 2425 public int getPreferredChildModeIndex(int modeIndex) { 2426 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2427 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager); 2428 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) { 2429 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam); 2430 } 2431 } 2432 return modeIndex; 2433 } 2434 2435 @Override 2436 public void onModeSelected(int modeIndex) { 2437 if (mCurrentModeIndex == modeIndex) { 2438 return; 2439 } 2440 2441 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START); 2442 // Record last used camera mode for quick switching 2443 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo) 2444 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2445 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2446 Keys.KEY_CAMERA_MODULE_LAST_USED, 2447 modeIndex); 2448 } 2449 2450 closeModule(mCurrentModule); 2451 2452 // Select the correct module index from the mode switcher index. 2453 modeIndex = getPreferredChildModeIndex(modeIndex); 2454 setModuleFromModeIndex(modeIndex); 2455 2456 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex); 2457 mCameraAppUI.addShutterListener(mCurrentModule); 2458 openModule(mCurrentModule); 2459 // Store the module index so we can use it the next time the Camera 2460 // starts up. 2461 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL, 2462 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex); 2463 } 2464 2465 /** 2466 * Shows the settings dialog. 2467 */ 2468 @Override 2469 public void onSettingsSelected() { 2470 UsageStatistics.instance().controlUsed( 2471 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS); 2472 Intent intent = new Intent(this, CameraSettingsActivity.class); 2473 startActivity(intent); 2474 } 2475 2476 @Override 2477 public void freezeScreenUntilPreviewReady() { 2478 mCameraAppUI.freezeScreenUntilPreviewReady(); 2479 } 2480 2481 @Override 2482 public int getModuleId(int modeIndex) { 2483 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2484 if (agent == null) { 2485 return -1; 2486 } 2487 return agent.getModuleId(); 2488 } 2489 2490 /** 2491 * Sets the mCurrentModuleIndex, creates a new module instance for the given 2492 * index an sets it as mCurrentModule. 2493 */ 2494 private void setModuleFromModeIndex(int modeIndex) { 2495 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex); 2496 if (agent == null) { 2497 return; 2498 } 2499 if (!agent.requestAppForCamera()) { 2500 mCameraController.closeCamera(true); 2501 } 2502 mCurrentModeIndex = agent.getModuleId(); 2503 mCurrentModule = (CameraModule) agent.createModule(this, getIntent()); 2504 } 2505 2506 @Override 2507 public SettingsManager getSettingsManager() { 2508 return mSettingsManager; 2509 } 2510 2511 @Override 2512 public ResolutionSetting getResolutionSetting() { 2513 return mResolutionSetting; 2514 } 2515 2516 @Override 2517 public CameraServices getServices() { 2518 return CameraServicesImpl.instance(); 2519 } 2520 2521 @Override 2522 public FatalErrorHandler getFatalErrorHandler() { 2523 return mFatalErrorHandler; 2524 } 2525 2526 public List<String> getSupportedModeNames() { 2527 List<Integer> indices = mModuleManager.getSupportedModeIndexList(); 2528 List<String> supported = new ArrayList<String>(); 2529 2530 for (Integer modeIndex : indices) { 2531 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext); 2532 if (name != null && !name.equals("")) { 2533 supported.add(name); 2534 } 2535 } 2536 return supported; 2537 } 2538 2539 @Override 2540 public ButtonManager getButtonManager() { 2541 if (mButtonManager == null) { 2542 mButtonManager = new ButtonManager(this); 2543 } 2544 return mButtonManager; 2545 } 2546 2547 @Override 2548 public SoundPlayer getSoundPlayer() { 2549 return mSoundPlayer; 2550 } 2551 2552 /** 2553 * Launches an ACTION_EDIT intent for the given local data item. If 2554 * 'withTinyPlanet' is set, this will show a disambig dialog first to let 2555 * the user start either the tiny planet editor or another photo editor. 2556 * 2557 * @param data The data item to edit. 2558 */ 2559 public void launchEditor(FilmstripItem data) { 2560 Intent intent = new Intent(Intent.ACTION_EDIT) 2561 .setDataAndType(data.getData().getUri(), data.getData().getMimeType()) 2562 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 2563 try { 2564 launchActivityByIntent(intent); 2565 } catch (ActivityNotFoundException e) { 2566 final String msgEditWith = getResources().getString(R.string.edit_with); 2567 launchActivityByIntent(Intent.createChooser(intent, msgEditWith)); 2568 } 2569 } 2570 2571 @Override 2572 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { 2573 super.onCreateContextMenu(menu, v, menuInfo); 2574 2575 MenuInflater inflater = getMenuInflater(); 2576 inflater.inflate(R.menu.filmstrip_context_menu, menu); 2577 } 2578 2579 @Override 2580 public boolean onContextItemSelected(MenuItem item) { 2581 switch (item.getItemId()) { 2582 case R.id.tiny_planet_editor: 2583 mMyFilmstripBottomControlListener.onTinyPlanet(); 2584 return true; 2585 case R.id.photo_editor: 2586 mMyFilmstripBottomControlListener.onEdit(); 2587 return true; 2588 } 2589 return false; 2590 } 2591 2592 /** 2593 * Launch the tiny planet editor. 2594 * 2595 * @param data The data must be a 360 degree stereographically mapped 2596 * panoramic image. It will not be modified, instead a new item 2597 * with the result will be added to the filmstrip. 2598 */ 2599 public void launchTinyPlanetEditor(FilmstripItem data) { 2600 TinyPlanetFragment fragment = new TinyPlanetFragment(); 2601 Bundle bundle = new Bundle(); 2602 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString()); 2603 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle()); 2604 fragment.setArguments(bundle); 2605 fragment.show(getFragmentManager(), "tiny_planet"); 2606 } 2607 2608 /** 2609 * Returns what UI mode (capture mode or filmstrip) we are in. 2610 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode} 2611 */ 2612 private int currentUserInterfaceMode() { 2613 int mode = NavigationChange.Mode.UNKNOWN_MODE; 2614 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) { 2615 mode = NavigationChange.Mode.PHOTO_CAPTURE; 2616 } 2617 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) { 2618 mode = NavigationChange.Mode.VIDEO_CAPTURE; 2619 } 2620 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) { 2621 mode = NavigationChange.Mode.LENS_BLUR; 2622 } 2623 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) { 2624 mode = NavigationChange.Mode.HDR_PLUS; 2625 } 2626 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) { 2627 mode = NavigationChange.Mode.PHOTO_SPHERE; 2628 } 2629 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) { 2630 mode = NavigationChange.Mode.PANORAMA; 2631 } 2632 if (mFilmstripVisible) { 2633 mode = NavigationChange.Mode.FILMSTRIP; 2634 } 2635 return mode; 2636 } 2637 2638 private void openModule(CameraModule module) { 2639 module.init(this, isSecureCamera(), isCaptureIntent()); 2640 module.hardResetSettings(mSettingsManager); 2641 // Hide accessibility zoom UI by default. Modules will enable it themselves if required. 2642 getCameraAppUI().hideAccessibilityZoomUI(); 2643 if (!mPaused) { 2644 module.resume(); 2645 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(), 2646 NavigationChange.InteractionCause.BUTTON); 2647 updatePreviewVisibility(); 2648 } 2649 } 2650 2651 private void closeModule(CameraModule module) { 2652 module.pause(); 2653 mCameraAppUI.clearModuleUI(); 2654 } 2655 2656 private void performDeletion() { 2657 if (!mPendingDeletion) { 2658 return; 2659 } 2660 hideUndoDeletionBar(false); 2661 mDataAdapter.executeDeletion(); 2662 } 2663 2664 public void showUndoDeletionBar() { 2665 if (mPendingDeletion) { 2666 performDeletion(); 2667 } 2668 Log.v(TAG, "showing undo bar"); 2669 mPendingDeletion = true; 2670 if (mUndoDeletionBar == null) { 2671 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar, 2672 mAboveFilmstripControlLayout, true); 2673 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar); 2674 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button); 2675 button.setOnClickListener(new View.OnClickListener() { 2676 @Override 2677 public void onClick(View view) { 2678 mDataAdapter.undoDeletion(); 2679 // Fix for b/21666018: When undoing a delete in Fullscreen 2680 // mode, just flip 2681 // back to the filmstrip to force a refresh. 2682 if (mFilmstripController.inFullScreen()) { 2683 mFilmstripController.goToFilmstrip(); 2684 } 2685 hideUndoDeletionBar(true); 2686 } 2687 }); 2688 // Setting undo bar clickable to avoid touch events going through 2689 // the bar to the buttons (eg. edit button, etc) underneath the bar. 2690 mUndoDeletionBar.setClickable(true); 2691 // When there is user interaction going on with the undo button, we 2692 // do not want to hide the undo bar. 2693 button.setOnTouchListener(new View.OnTouchListener() { 2694 @Override 2695 public boolean onTouch(View v, MotionEvent event) { 2696 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 2697 mIsUndoingDeletion = true; 2698 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { 2699 mIsUndoingDeletion = false; 2700 } 2701 return false; 2702 } 2703 }); 2704 } 2705 mUndoDeletionBar.setAlpha(0f); 2706 mUndoDeletionBar.setVisibility(View.VISIBLE); 2707 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start(); 2708 } 2709 2710 private void hideUndoDeletionBar(boolean withAnimation) { 2711 Log.v(TAG, "Hiding undo deletion bar"); 2712 mPendingDeletion = false; 2713 if (mUndoDeletionBar != null) { 2714 if (withAnimation) { 2715 mUndoDeletionBar.animate().setDuration(200).alpha(0f) 2716 .setListener(new Animator.AnimatorListener() { 2717 @Override 2718 public void onAnimationStart(Animator animation) { 2719 // Do nothing. 2720 } 2721 2722 @Override 2723 public void onAnimationEnd(Animator animation) { 2724 mUndoDeletionBar.setVisibility(View.GONE); 2725 } 2726 2727 @Override 2728 public void onAnimationCancel(Animator animation) { 2729 // Do nothing. 2730 } 2731 2732 @Override 2733 public void onAnimationRepeat(Animator animation) { 2734 // Do nothing. 2735 } 2736 }).start(); 2737 } else { 2738 mUndoDeletionBar.setVisibility(View.GONE); 2739 } 2740 } 2741 } 2742 2743 /** 2744 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in 2745 * capture intent. 2746 * 2747 * @param enable {@code true} to enable swipe. 2748 */ 2749 public void setSwipingEnabled(boolean enable) { 2750 // TODO: Bring back the functionality. 2751 if (isCaptureIntent()) { 2752 // lockPreview(true); 2753 } else { 2754 // lockPreview(!enable); 2755 } 2756 } 2757 2758 // Accessor methods for getting latency times used in performance testing 2759 public long getFirstPreviewTime() { 2760 if (mCurrentModule instanceof PhotoModule) { 2761 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime(); 2762 if (coverHiddenTime != -1) { 2763 return coverHiddenTime - mOnCreateTime; 2764 } 2765 } 2766 return -1; 2767 } 2768 2769 public long getAutoFocusTime() { 2770 return (mCurrentModule instanceof PhotoModule) ? 2771 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; 2772 } 2773 2774 public long getShutterLag() { 2775 return (mCurrentModule instanceof PhotoModule) ? 2776 ((PhotoModule) mCurrentModule).mShutterLag : -1; 2777 } 2778 2779 public long getShutterToPictureDisplayedTime() { 2780 return (mCurrentModule instanceof PhotoModule) ? 2781 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; 2782 } 2783 2784 public long getPictureDisplayedToJpegCallbackTime() { 2785 return (mCurrentModule instanceof PhotoModule) ? 2786 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; 2787 } 2788 2789 public long getJpegCallbackFinishTime() { 2790 return (mCurrentModule instanceof PhotoModule) ? 2791 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; 2792 } 2793 2794 public long getCaptureStartTime() { 2795 return (mCurrentModule instanceof PhotoModule) ? 2796 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; 2797 } 2798 2799 public boolean isRecording() { 2800 return (mCurrentModule instanceof VideoModule) ? 2801 ((VideoModule) mCurrentModule).isRecording() : false; 2802 } 2803 2804 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() { 2805 return mCameraController; 2806 } 2807 2808 // For debugging purposes only. 2809 public CameraModule getCurrentModule() { 2810 return mCurrentModule; 2811 } 2812 2813 @Override 2814 public void showTutorial(AbstractTutorialOverlay tutorial) { 2815 mCameraAppUI.showTutorial(tutorial, getLayoutInflater()); 2816 } 2817 2818 @Override 2819 public void finishActivityWithIntentCompleted(Intent resultIntent) { 2820 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent); 2821 } 2822 2823 @Override 2824 public void finishActivityWithIntentCanceled() { 2825 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent()); 2826 } 2827 2828 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) { 2829 mResultCodeForTesting = resultCode; 2830 mResultDataForTesting = resultIntent; 2831 setResult(resultCode, resultIntent); 2832 finish(); 2833 } 2834 2835 private void keepScreenOnForAWhile() { 2836 if (mKeepScreenOn) { 2837 return; 2838 } 2839 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2840 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2841 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS); 2842 } 2843 2844 private void resetScreenOn() { 2845 mKeepScreenOn = false; 2846 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG); 2847 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2848 } 2849 2850 /** 2851 * @return {@code true} if the Gallery is launched successfully. 2852 */ 2853 private boolean startGallery() { 2854 if (mGalleryIntent == null) { 2855 return false; 2856 } 2857 try { 2858 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY, 2859 NavigationChange.InteractionCause.BUTTON); 2860 Intent startGalleryIntent = new Intent(mGalleryIntent); 2861 int currentIndex = mFilmstripController.getCurrentAdapterIndex(); 2862 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex); 2863 if (currentFilmstripItem != null) { 2864 GalleryHelper.setContentUri(startGalleryIntent, 2865 currentFilmstripItem.getData().getUri()); 2866 } 2867 launchActivityByIntent(startGalleryIntent); 2868 } catch (ActivityNotFoundException e) { 2869 Log.w(TAG, "Failed to launch gallery activity, closing"); 2870 } 2871 return false; 2872 } 2873 2874 private void setNfcBeamPushUriFromData(FilmstripItem data) { 2875 final Uri uri = data.getData().getUri(); 2876 if (uri != Uri.EMPTY) { 2877 mNfcPushUris[0] = uri; 2878 } else { 2879 mNfcPushUris[0] = null; 2880 } 2881 } 2882 2883 /** 2884 * Updates the visibility of the filmstrip bottom controls and action bar. 2885 */ 2886 private void updateUiByData(final int index) { 2887 final FilmstripItem currentData = mDataAdapter.getItemAt(index); 2888 if (currentData == null) { 2889 Log.w(TAG, "Current data ID not found."); 2890 hideSessionProgress(); 2891 return; 2892 } 2893 updateActionBarMenu(currentData); 2894 2895 /* Bottom controls. */ 2896 updateBottomControlsByData(currentData); 2897 2898 if (isSecureCamera()) { 2899 // We cannot show buttons in secure camera since go to other 2900 // activities might create a security hole. 2901 mCameraAppUI.getFilmstripBottomControls().hideControls(); 2902 return; 2903 } 2904 2905 setNfcBeamPushUriFromData(currentData); 2906 2907 if (!mDataAdapter.isMetadataUpdatedAt(index)) { 2908 mDataAdapter.updateMetadataAt(index); 2909 } 2910 } 2911 2912 /** 2913 * Updates the bottom controls based on the data. 2914 */ 2915 private void updateBottomControlsByData(final FilmstripItem currentData) { 2916 2917 final CameraAppUI.BottomPanel filmstripBottomPanel = 2918 mCameraAppUI.getFilmstripBottomControls(); 2919 filmstripBottomPanel.showControls(); 2920 filmstripBottomPanel.setEditButtonVisibility( 2921 currentData.getAttributes().canEdit()); 2922 filmstripBottomPanel.setShareButtonVisibility( 2923 currentData.getAttributes().canShare()); 2924 filmstripBottomPanel.setDeleteButtonVisibility( 2925 currentData.getAttributes().canDelete()); 2926 2927 /* Progress bar */ 2928 2929 Uri contentUri = currentData.getData().getUri(); 2930 CaptureSessionManager sessionManager = getServices() 2931 .getCaptureSessionManager(); 2932 2933 if (sessionManager.hasErrorMessage(contentUri)) { 2934 showProcessError(sessionManager.getErrorMessageId(contentUri)); 2935 } else { 2936 filmstripBottomPanel.hideProgressError(); 2937 CaptureSession session = sessionManager.getSession(contentUri); 2938 2939 if (session != null) { 2940 int sessionProgress = session.getProgress(); 2941 2942 if (sessionProgress < 0) { 2943 hideSessionProgress(); 2944 } else { 2945 int progressMessageId = session.getProgressMessageId(); 2946 showSessionProgress(progressMessageId); 2947 updateSessionProgress(sessionProgress); 2948 } 2949 } else { 2950 hideSessionProgress(); 2951 } 2952 } 2953 2954 /* View button */ 2955 2956 // We need to add this to a separate DB. 2957 final int viewButtonVisibility; 2958 if (currentData.getMetadata().isUsePanoramaViewer()) { 2959 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE; 2960 } else if (currentData.getMetadata().isHasRgbzData()) { 2961 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS; 2962 } else { 2963 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE; 2964 } 2965 2966 filmstripBottomPanel.setTinyPlanetEnabled( 2967 currentData.getMetadata().isPanorama360()); 2968 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility); 2969 } 2970 2971 private void showDetailsDialog(int index) { 2972 final FilmstripItem data = mDataAdapter.getItemAt(index); 2973 if (data == null) { 2974 return; 2975 } 2976 Optional<MediaDetails> details = data.getMediaDetails(); 2977 if (!details.isPresent()) { 2978 return; 2979 } 2980 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get()); 2981 detailDialog.show(); 2982 UsageStatistics.instance().mediaInteraction( 2983 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS, 2984 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index)); 2985 } 2986 2987 /** 2988 * Show or hide action bar items depending on current data type. 2989 */ 2990 private void updateActionBarMenu(FilmstripItem data) { 2991 if (mActionBarMenu == null) { 2992 return; 2993 } 2994 2995 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details); 2996 if (detailsMenuItem == null) { 2997 return; 2998 } 2999 3000 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo(); 3001 detailsMenuItem.setVisible(showDetails); 3002 } 3003} 3004