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