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