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