2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.camera;
20 import android.Manifest;
21 import android.animation.Animator;
22 import android.app.ActionBar;
23 import android.app.Activity;
24 import android.app.Dialog;
25 import android.content.ActivityNotFoundException;
26 import android.content.BroadcastReceiver;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.PackageManager;
33 import android.content.res.Configuration;
34 import android.graphics.Bitmap;
35 import android.graphics.Matrix;
36 import android.graphics.RectF;
37 import android.graphics.SurfaceTexture;
38 import android.graphics.drawable.ColorDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.net.Uri;
41 import android.nfc.NfcAdapter;
42 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
43 import android.nfc.NfcEvent;
44 import android.os.AsyncTask;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.Looper;
49 import android.os.Message;
50 import android.provider.MediaStore;
51 import android.provider.Settings;
52 import android.text.TextUtils;
53 import android.util.CameraPerformanceTracker;
54 import android.view.ContextMenu;
55 import android.view.ContextMenu.ContextMenuInfo;
56 import android.view.KeyEvent;
57 import android.view.Menu;
58 import android.view.MenuInflater;
59 import android.view.MenuItem;
60 import android.view.MotionEvent;
61 import android.view.View;
62 import android.view.View.OnSystemUiVisibilityChangeListener;
63 import android.view.ViewGroup;
64 import android.view.Window;
65 import android.view.WindowManager;
66 import android.widget.FrameLayout;
67 import android.widget.ImageView;
68 import android.widget.ShareActionProvider;
70 import com.android.camera.app.AppController;
71 import com.android.camera.app.CameraAppUI;
72 import com.android.camera.app.CameraController;
73 import com.android.camera.app.CameraProvider;
74 import com.android.camera.app.CameraServices;
75 import com.android.camera.app.CameraServicesImpl;
76 import com.android.camera.app.FirstRunDialog;
77 import com.android.camera.app.LocationManager;
78 import com.android.camera.app.MemoryManager;
79 import com.android.camera.app.MemoryQuery;
80 import com.android.camera.app.ModuleManager;
81 import com.android.camera.app.ModuleManager.ModuleAgent;
82 import com.android.camera.app.ModuleManagerImpl;
83 import com.android.camera.app.MotionManager;
84 import com.android.camera.app.OrientationManager;
85 import com.android.camera.app.OrientationManagerImpl;
86 import com.android.camera.data.CameraFilmstripDataAdapter;
87 import com.android.camera.data.FilmstripContentObserver;
88 import com.android.camera.data.FilmstripItem;
89 import com.android.camera.data.FilmstripItemData;
90 import com.android.camera.data.FilmstripItemType;
91 import com.android.camera.data.FilmstripItemUtils;
92 import com.android.camera.data.FixedLastProxyAdapter;
93 import com.android.camera.data.GlideFilmstripManager;
94 import com.android.camera.data.LocalFilmstripDataAdapter;
95 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
96 import com.android.camera.data.MediaDetails;
97 import com.android.camera.data.MetadataLoader;
98 import com.android.camera.data.PhotoDataFactory;
99 import com.android.camera.data.PhotoItem;
100 import com.android.camera.data.PhotoItemFactory;
101 import com.android.camera.data.PlaceholderItem;
102 import com.android.camera.data.SessionItem;
103 import com.android.camera.data.VideoDataFactory;
104 import com.android.camera.data.VideoItemFactory;
105 import com.android.camera.debug.Log;
106 import com.android.camera.device.ActiveCameraDeviceTracker;
107 import com.android.camera.device.CameraId;
108 import com.android.camera.filmstrip.FilmstripContentPanel;
109 import com.android.camera.filmstrip.FilmstripController;
110 import com.android.camera.module.ModuleController;
111 import com.android.camera.module.ModulesInfo;
112 import com.android.camera.one.OneCameraException;
113 import com.android.camera.one.OneCameraManager;
114 import com.android.camera.one.OneCameraModule;
115 import com.android.camera.one.OneCameraOpener;
116 import com.android.camera.one.config.OneCameraFeatureConfig;
117 import com.android.camera.one.config.OneCameraFeatureConfigCreator;
118 import com.android.camera.session.CaptureSession;
119 import com.android.camera.session.CaptureSessionManager;
120 import com.android.camera.session.CaptureSessionManager.SessionListener;
121 import com.android.camera.settings.AppUpgrader;
122 import com.android.camera.settings.CameraSettingsActivity;
123 import com.android.camera.settings.Keys;
124 import com.android.camera.settings.PictureSizeLoader;
125 import com.android.camera.settings.ResolutionSetting;
126 import com.android.camera.settings.ResolutionUtil;
127 import com.android.camera.settings.SettingsManager;
128 import com.android.camera.stats.UsageStatistics;
129 import com.android.camera.stats.profiler.Profile;
130 import com.android.camera.stats.profiler.Profiler;
131 import com.android.camera.stats.profiler.Profilers;
132 import com.android.camera.tinyplanet.TinyPlanetFragment;
133 import com.android.camera.ui.AbstractTutorialOverlay;
134 import com.android.camera.ui.DetailsDialog;
135 import com.android.camera.ui.MainActivityLayout;
136 import com.android.camera.ui.ModeListView;
137 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
138 import com.android.camera.ui.PreviewStatusListener;
139 import com.android.camera.util.ApiHelper;
140 import com.android.camera.util.Callback;
141 import com.android.camera.util.CameraUtil;
142 import com.android.camera.util.GalleryHelper;
143 import com.android.camera.util.GcamHelper;
144 import com.android.camera.util.GoogleHelpHelper;
145 import com.android.camera.util.IntentHelper;
146 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
147 import com.android.camera.util.QuickActivity;
148 import com.android.camera.util.ReleaseHelper;
149 import com.android.camera.widget.FilmstripView;
150 import com.android.camera.widget.Preloader;
151 import com.android.camera2.R;
152 import com.android.ex.camera2.portability.CameraAgent;
153 import com.android.ex.camera2.portability.CameraAgentFactory;
154 import com.android.ex.camera2.portability.CameraExceptionHandler;
155 import com.android.ex.camera2.portability.CameraSettings;
156 import com.bumptech.glide.Glide;
157 import com.bumptech.glide.GlideBuilder;
158 import com.bumptech.glide.MemoryCategory;
159 import com.bumptech.glide.load.DecodeFormat;
160 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
162 import com.google.common.base.Optional;
163 import com.google.common.logging.eventprotos;
164 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
165 import com.google.common.logging.eventprotos.MediaInteraction;
166 import com.google.common.logging.eventprotos.NavigationChange;
169 import java.lang.ref.WeakReference;
170 import java.util.ArrayList;
171 import java.util.HashMap;
172 import java.util.List;
174 public class CameraActivity extends QuickActivity
175 implements AppController, CameraAgent.CameraOpenCallback,
176 ShareActionProvider.OnShareTargetSelectedListener {
178 private static final Log.Tag TAG = new Log.Tag("CameraActivity");
180 private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
181 "android.media.action.STILL_IMAGE_CAMERA_SECURE";
182 public static final String ACTION_IMAGE_CAPTURE_SECURE =
183 "android.media.action.IMAGE_CAPTURE_SECURE";
185 // The intent extra for camera from secure lock screen. True if the gallery
186 // should only show newly captured pictures. sSecureAlbumId does not
187 // increment. This is used when switching between camera, camcorder, and
188 // panorama. If the extra is not set, it is in the normal camera mode.
189 public static final String SECURE_CAMERA_EXTRA = "secure_camera";
191 private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
192 private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
193 /** Load metadata for 10 items ahead of our current. */
194 private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
195 private static final int PERMISSIONS_ACTIVITY_REQUEST_CODE = 1;
196 private static final int PERMISSIONS_RESULT_CODE_OK = 1;
197 private static final int PERMISSIONS_RESULT_CODE_FAILED = 2;
199 /** Should be used wherever a context is needed. */
200 private Context mAppContext;
203 * Camera fatal error handling:
204 * 1) Present error dialog to guide users to exit the app.
205 * 2) If users hit home button, onPause should just call finish() to exit the app.
207 private boolean mCameraFatalError = false;
210 * Whether onResume should reset the view to the preview.
212 private boolean mResetToPreviewOnResume = true;
215 * This data adapter is used by FilmStripView.
217 private VideoItemFactory mVideoItemFactory;
218 private PhotoItemFactory mPhotoItemFactory;
219 private LocalFilmstripDataAdapter mDataAdapter;
221 private ActiveCameraDeviceTracker mActiveCameraDeviceTracker;
222 private OneCameraOpener mOneCameraOpener;
223 private OneCameraManager mOneCameraManager;
224 private SettingsManager mSettingsManager;
225 private ResolutionSetting mResolutionSetting;
226 private ModeListView mModeListView;
227 private boolean mModeListVisible = false;
228 private int mCurrentModeIndex;
229 private CameraModule mCurrentModule;
230 private ModuleManagerImpl mModuleManager;
231 private FrameLayout mAboveFilmstripControlLayout;
232 private FilmstripController mFilmstripController;
233 private boolean mFilmstripVisible;
234 /** Whether the filmstrip fully covers the preview. */
235 private boolean mFilmstripCoversPreview = false;
236 private int mResultCodeForTesting;
237 private Intent mResultDataForTesting;
238 private OnScreenHint mStorageHint;
239 private final Object mStorageSpaceLock = new Object();
240 private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
241 private boolean mAutoRotateScreen;
242 private boolean mSecureCamera;
243 private OrientationManagerImpl mOrientationManager;
244 private LocationManager mLocationManager;
245 private ButtonManager mButtonManager;
246 private Handler mMainHandler;
247 private PanoramaViewHelper mPanoramaViewHelper;
248 private ActionBar mActionBar;
249 private ViewGroup mUndoDeletionBar;
250 private boolean mIsUndoingDeletion = false;
251 private boolean mIsActivityRunning = false;
252 private FatalErrorHandler mFatalErrorHandler;
253 private boolean mHasCriticalPermissions;
255 private final Uri[] mNfcPushUris = new Uri[1];
257 private FilmstripContentObserver mLocalImagesObserver;
258 private FilmstripContentObserver mLocalVideosObserver;
260 private boolean mPendingDeletion = false;
262 private CameraController mCameraController;
263 private boolean mPaused;
264 private CameraAppUI mCameraAppUI;
266 private Intent mGalleryIntent;
267 private long mOnCreateTime;
269 private Menu mActionBarMenu;
270 private Preloader<Integer, AsyncTask> mPreloader;
272 /** Can be used to play custom sounds. */
273 private SoundPlayer mSoundPlayer;
275 /** Holds configuration for various OneCamera features. */
276 private OneCameraFeatureConfig mFeatureConfig;
278 private static final int LIGHTS_OUT_DELAY_MS = 4000;
279 private final int BASE_SYS_UI_VISIBILITY =
280 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
281 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
282 private final Runnable mLightsOutRunnable = new Runnable() {
285 getWindow().getDecorView().setSystemUiVisibility(
286 BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
289 private MemoryManager mMemoryManager;
290 private MotionManager mMotionManager;
291 private final Profiler mProfiler = Profilers.instance().guard();
293 /** First run dialog */
294 private FirstRunDialog mFirstRunDialog;
297 public CameraAppUI getCameraAppUI() {
302 public ModuleManager getModuleManager() {
303 return mModuleManager;
307 * Close activity when secure app passes lock screen or screen turns
310 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
312 public void onReceive(Context context, Intent intent) {
318 * Whether the screen is kept turned on.
320 private boolean mKeepScreenOn;
321 private int mLastLayoutOrientation;
322 private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
323 new CameraAppUI.BottomPanel.Listener() {
326 * If the current photo is a photo sphere, this will launch the
327 * Photo Sphere panorama viewer.
330 public void onExternalViewer() {
331 if (mPanoramaViewHelper == null) {
334 final FilmstripItem data = getCurrentLocalData();
336 Log.w(TAG, "Cannot open null data.");
339 final Uri contentUri = data.getData().getUri();
340 if (contentUri == Uri.EMPTY) {
341 Log.w(TAG, "Cannot open empty URL.");
345 if (data.getMetadata().isUsePanoramaViewer()) {
346 mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
347 } else if (data.getMetadata().isHasRgbzData()) {
348 mPanoramaViewHelper.showRgbz(contentUri);
349 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
350 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
351 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
352 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
353 mCameraAppUI.clearClingForViewer(
354 CameraAppUI.BottomPanel.VIEWER_REFOCUS);
360 public void onEdit() {
361 FilmstripItem data = getCurrentLocalData();
363 Log.w(TAG, "Cannot edit null data.");
366 final int currentDataId = getCurrentDataId();
367 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
369 MediaInteraction.InteractionType.EDIT,
370 NavigationChange.InteractionCause.BUTTON,
371 fileAgeFromAdapterAtIndex(currentDataId));
376 public void onTinyPlanet() {
377 FilmstripItem data = getCurrentLocalData();
379 Log.w(TAG, "Cannot edit tiny planet on null data.");
382 launchTinyPlanetEditor(data);
386 public void onDelete() {
387 final int currentDataId = getCurrentDataId();
388 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
390 MediaInteraction.InteractionType.DELETE,
391 NavigationChange.InteractionCause.BUTTON,
392 fileAgeFromAdapterAtIndex(currentDataId));
393 removeItemAt(currentDataId);
397 public void onShare() {
398 final FilmstripItem data = getCurrentLocalData();
400 Log.w(TAG, "Cannot share null data.");
404 final int currentDataId = getCurrentDataId();
405 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
407 MediaInteraction.InteractionType.SHARE,
408 NavigationChange.InteractionCause.BUTTON,
409 fileAgeFromAdapterAtIndex(currentDataId));
410 // If applicable, show release information before this item
412 if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
413 ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
414 new Callback<Void>() {
416 public void onCallback(Void result) {
425 private void share(FilmstripItem data) {
426 Intent shareIntent = getShareIntentByData(data);
427 if (shareIntent != null) {
429 launchActivityByIntent(shareIntent);
430 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
431 } catch (ActivityNotFoundException ex) {
437 private int getCurrentDataId() {
438 return mFilmstripController.getCurrentAdapterIndex();
441 private FilmstripItem getCurrentLocalData() {
442 return mDataAdapter.getItemAt(getCurrentDataId());
446 * Sets up the share intent and NFC properly according to the
449 * @param item The data to be shared.
451 private Intent getShareIntentByData(final FilmstripItem item) {
452 Intent intent = null;
453 final Uri contentUri = item.getData().getUri();
454 final String msgShareTo = getResources().getString(R.string.share_to);
456 if (item.getMetadata().isPanorama360() &&
457 item.getData().getUri() != Uri.EMPTY) {
458 intent = new Intent(Intent.ACTION_SEND);
459 intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
460 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
461 } else if (item.getAttributes().canShare()) {
462 final String mimeType = item.getData().getMimeType();
463 intent = getShareIntentFromType(mimeType);
464 if (intent != null) {
465 intent.putExtra(Intent.EXTRA_STREAM, contentUri);
466 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
468 intent = Intent.createChooser(intent, msgShareTo);
474 * Get the share intent according to the mimeType
476 * @param mimeType The mimeType of current data.
477 * @return the video/image's ShareIntent or null if mimeType is
480 private Intent getShareIntentFromType(String mimeType) {
481 // Lazily create the intent object.
482 Intent intent = new Intent(Intent.ACTION_SEND);
483 if (mimeType.startsWith("video/")) {
484 intent.setType("video/*");
486 if (mimeType.startsWith("image/")) {
487 intent.setType("image/*");
489 Log.w(TAG, "unsupported mimeType " + mimeType);
496 public void onProgressErrorClicked() {
497 FilmstripItem data = getCurrentLocalData();
498 getServices().getCaptureSessionManager().removeErrorMessage(
499 data.getData().getUri());
500 updateBottomControlsByData(data);
505 public void onCameraOpened(CameraAgent.CameraProxy camera) {
506 Log.v(TAG, "onCameraOpened");
508 // We've paused, but just asynchronously opened the camera. Close it
509 // because we should be releasing the camera when paused to allow
510 // other apps to access it.
511 Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
512 mCameraController.closeCamera(false);
516 if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
517 // We shouldn't be here. Just close the camera and leave.
518 mCameraController.closeCamera(false);
519 throw new IllegalStateException("Camera opened but the module shouldn't be " +
522 if (mCurrentModule != null) {
523 resetExposureCompensationToDefault(camera);
525 mCurrentModule.onCameraAvailable(camera);
526 } catch (RuntimeException ex) {
527 Log.e(TAG, "Error connecting to camera", ex);
528 mFatalErrorHandler.onCameraOpenFailure();
531 Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
533 Log.v(TAG, "invoking onChangeCamera");
534 mCameraAppUI.onChangeCamera();
537 private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
538 // Reset the exposure compensation before handing the camera to module.
539 CameraSettings cameraSettings = camera.getSettings();
540 cameraSettings.setExposureCompensationIndex(0);
541 camera.applySettings(cameraSettings);
545 public void onCameraDisabled(int cameraId) {
546 Log.w(TAG, "Camera disabled: " + cameraId);
547 mFatalErrorHandler.onCameraDisabledFailure();
551 public void onDeviceOpenFailure(int cameraId, String info) {
552 Log.w(TAG, "Camera open failure: " + info);
553 mFatalErrorHandler.onCameraOpenFailure();
557 public void onDeviceOpenedAlready(int cameraId, String info) {
558 Log.w(TAG, "Camera open already: " + cameraId + "," + info);
559 mFatalErrorHandler.onGenericCameraAccessFailure();
563 public void onReconnectionFailure(CameraAgent mgr, String info) {
564 Log.w(TAG, "Camera reconnection failure:" + info);
565 mFatalErrorHandler.onCameraReconnectFailure();
568 private static class MainHandler extends Handler {
569 final WeakReference<CameraActivity> mActivity;
571 public MainHandler(CameraActivity activity, Looper looper) {
573 mActivity = new WeakReference<CameraActivity>(activity);
577 public void handleMessage(Message msg) {
578 CameraActivity activity = mActivity.get();
579 if (activity == null) {
584 case MSG_CLEAR_SCREEN_ON_FLAG: {
585 if (!activity.mPaused) {
586 activity.getWindow().clearFlags(
587 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
595 private String fileNameFromAdapterAtIndex(int index) {
596 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
597 if (filmstripItem == null) {
601 File localFile = new File(filmstripItem.getData().getFilePath());
602 return localFile.getName();
605 private float fileAgeFromAdapterAtIndex(int index) {
606 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
607 if (filmstripItem == null) {
611 File localFile = new File(filmstripItem.getData().getFilePath());
612 return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
615 private final FilmstripContentPanel.Listener mFilmstripListener =
616 new FilmstripContentPanel.Listener() {
619 public void onSwipeOut() {
623 public void onSwipeOutBegin() {
625 mCameraAppUI.hideBottomControls();
626 mFilmstripCoversPreview = false;
627 updatePreviewVisibility();
631 public void onFilmstripHidden() {
632 mFilmstripVisible = false;
633 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
634 NavigationChange.InteractionCause.SWIPE_RIGHT);
635 CameraActivity.this.setFilmstripUiVisibility(false);
636 // When the user hide the filmstrip (either swipe out or
637 // tap on back key) we move to the first item so next time
638 // when the user swipe in the filmstrip, the most recent
640 mFilmstripController.goToFirstItem();
644 public void onFilmstripShown() {
645 mFilmstripVisible = true;
646 mCameraAppUI.hideCaptureIndicator();
647 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
648 NavigationChange.InteractionCause.SWIPE_LEFT);
649 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
653 public void onFocusedDataLongPressed(int adapterIndex) {
658 public void onFocusedDataPromoted(int adapterIndex) {
659 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
661 MediaInteraction.InteractionType.DELETE,
662 NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
664 removeItemAt(adapterIndex);
668 public void onFocusedDataDemoted(int adapterIndex) {
669 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
671 MediaInteraction.InteractionType.DELETE,
672 NavigationChange.InteractionCause.SWIPE_DOWN,
673 fileAgeFromAdapterAtIndex(adapterIndex));
674 removeItemAt(adapterIndex);
678 public void onEnterFullScreenUiShown(int adapterIndex) {
679 if (mFilmstripVisible) {
680 CameraActivity.this.setFilmstripUiVisibility(true);
685 public void onLeaveFullScreenUiShown(int adapterIndex) {
690 public void onEnterFullScreenUiHidden(int adapterIndex) {
691 if (mFilmstripVisible) {
692 CameraActivity.this.setFilmstripUiVisibility(false);
697 public void onLeaveFullScreenUiHidden(int adapterIndex) {
702 public void onEnterFilmstrip(int adapterIndex) {
703 if (mFilmstripVisible) {
704 CameraActivity.this.setFilmstripUiVisibility(true);
709 public void onLeaveFilmstrip(int adapterIndex) {
714 public void onDataReloaded() {
715 if (!mFilmstripVisible) {
718 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
722 public void onDataUpdated(int adapterIndex) {
723 if (!mFilmstripVisible) {
726 updateUiByData(mFilmstripController.getCurrentAdapterIndex());
730 public void onEnterZoomView(int adapterIndex) {
731 if (mFilmstripVisible) {
732 CameraActivity.this.setFilmstripUiVisibility(false);
737 public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
738 final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
739 long ageMillis = System.currentTimeMillis()
740 - filmstripItem.getData().getLastModifiedDate().getTime();
742 // Do not log if items is to old or does not have a path (which is
743 // being used as a key).
744 if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
745 ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
748 File localFile = new File(filmstripItem.getData().getFilePath());
749 UsageStatistics.instance().mediaView(localFile.getName(),
750 filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
754 public void onDataFocusChanged(final int prevIndex, final int newIndex) {
755 if (!mFilmstripVisible) {
758 // TODO: This callback is UI event callback, should always
759 // happen on UI thread. Find the reason for this
760 // runOnUiThread() and fix it.
761 runOnUiThread(new Runnable() {
764 updateUiByData(newIndex);
770 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
771 mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
775 private final FilmstripItemListener mFilmstripItemListener =
776 new FilmstripItemListener() {
778 public void onMetadataUpdated(List<Integer> indexes) {
780 // Callback after the activity is paused.
783 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
784 for (Integer index : indexes) {
785 if (index == currentIndex) {
786 updateUiByData(index);
787 // Currently we have only 1 data can be matched.
788 // No need to look for more, break.
795 public void gotoGallery() {
796 UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
797 NavigationChange.InteractionCause.BUTTON);
799 mFilmstripController.goToNextItem();
803 * If 'visible' is false, this hides the action bar. Also maintains
804 * lights-out at all times.
806 * @param visible is false, this hides the action bar and filmstrip bottom
809 private void setFilmstripUiVisibility(boolean visible) {
810 mLightsOutRunnable.run();
811 mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
812 if (visible != mActionBar.isShowing()) {
815 mCameraAppUI.showBottomControls();
818 mCameraAppUI.hideBottomControls();
821 mFilmstripCoversPreview = visible;
822 updatePreviewVisibility();
825 private void hideSessionProgress() {
826 mCameraAppUI.getFilmstripBottomControls().hideProgress();
829 private void showSessionProgress(int messageId) {
830 CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
831 controls.setProgressText(messageId > 0 ? getString(messageId) : "");
832 controls.hideControls();
833 controls.hideProgressError();
834 controls.showProgress();
837 private void showProcessError(int messageId) {
838 mCameraAppUI.getFilmstripBottomControls().showProgressError(
839 messageId > 0 ? getString(messageId) : "");
842 private void updateSessionProgress(int progress) {
843 mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
846 private void updateSessionProgressText(int messageId) {
847 mCameraAppUI.getFilmstripBottomControls().setProgressText(
848 messageId > 0 ? getString(messageId) : "");
851 private void setupNfcBeamPush() {
852 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
853 if (adapter == null) {
857 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
859 adapter.setNdefPushMessage(null, CameraActivity.this);
863 adapter.setBeamPushUris(null, CameraActivity.this);
864 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
866 public Uri[] createBeamUris(NfcEvent event) {
869 }, CameraActivity.this);
873 public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
874 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
875 if (currentIndex < 0) {
878 UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
879 MediaInteraction.InteractionType.SHARE,
880 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
881 // TODO add intent.getComponent().getPackageName()
885 // Note: All callbacks come back on the main thread.
886 private final SessionListener mSessionListener =
887 new SessionListener() {
889 public void onSessionQueued(final Uri uri) {
890 Log.v(TAG, "onSessionQueued: " + uri);
891 if (!Storage.isSessionUri(uri)) {
894 Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri);
895 if (newData.isPresent()) {
896 mDataAdapter.addOrUpdate(newData.get());
901 public void onSessionUpdated(Uri uri) {
902 Log.v(TAG, "onSessionUpdated: " + uri);
903 mDataAdapter.refresh(uri);
907 public void onSessionDone(final Uri sessionUri) {
908 Log.v(TAG, "onSessionDone:" + sessionUri);
909 Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
910 if (contentUri == null) {
911 mDataAdapter.refresh(sessionUri);
914 PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
916 // This can be null if e.g. a session is canceled (e.g.
917 // through discard panorama). It might be worth adding
918 // onSessionCanceled or the like this interface.
919 if (newData == null) {
920 Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
924 final int pos = mDataAdapter.findByContentUri(sessionUri);
926 // We do not have a placeholder for this image, perhaps
927 // due to the activity crashing or being killed.
928 mDataAdapter.addOrUpdate(newData);
930 // Make the PhotoItem aware of the session placeholder, to
931 // allow it to make a smooth transition to its content if it
932 // the session item is currently visible.
933 FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos);
934 if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE
935 && mFilmstripController.isVisible(oldSessionData)) {
936 Log.v(TAG, "session item visible, setting transition placeholder");
937 newData.setSessionPlaceholderBitmap(
938 Storage.getPlaceholderForSession(sessionUri));
940 mDataAdapter.updateItemAt(pos, newData);
945 public void onSessionProgress(final Uri uri, final int progress) {
947 // Do nothing, there is no task for this URI.
950 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
951 if (currentIndex == -1) {
955 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
956 updateSessionProgress(progress);
961 public void onSessionProgressText(final Uri uri, final int messageId) {
962 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
963 if (currentIndex == -1) {
967 mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
968 updateSessionProgressText(messageId);
973 public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
974 // Don't show capture indicator in Photo Sphere.
975 final int photosphereModuleId = getApplicationContext().getResources()
977 R.integer.camera_mode_photosphere);
978 if (mCurrentModeIndex == photosphereModuleId) {
981 indicateCapture(indicator, rotationDegrees);
985 public void onSessionFailed(Uri uri, int failureMessageId,
986 boolean removeFromFilmstrip) {
987 Log.v(TAG, "onSessionFailed:" + uri);
989 int failedIndex = mDataAdapter.findByContentUri(uri);
990 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
992 if (currentIndex == failedIndex) {
993 updateSessionProgress(0);
994 showProcessError(failureMessageId);
995 mDataAdapter.refresh(uri);
997 if (removeFromFilmstrip) {
998 mFatalErrorHandler.onMediaStorageFailure();
999 mDataAdapter.removeAt(failedIndex);
1004 public void onSessionCanceled(Uri uri) {
1005 Log.v(TAG, "onSessionCanceled:" + uri);
1006 int failedIndex = mDataAdapter.findByContentUri(uri);
1007 mDataAdapter.removeAt(failedIndex);
1011 public void onSessionThumbnailUpdate(Bitmap bitmap) {
1015 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
1020 public Context getAndroidContext() {
1025 public OneCameraFeatureConfig getCameraFeatureConfig() {
1026 return mFeatureConfig;
1030 public Dialog createDialog() {
1031 return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
1035 public void launchActivityByIntent(Intent intent) {
1036 // Starting from L, we prefer not to start edit activity within camera's task.
1037 mResetToPreviewOnResume = false;
1038 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1040 startActivity(intent);
1044 public int getCurrentModuleIndex() {
1045 return mCurrentModeIndex;
1049 public String getModuleScope() {
1050 ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
1051 return SettingsManager.getModuleSettingScope(agent.getScopeNamespace());
1055 public String getCameraScope() {
1056 // if an unopen camera i.e. negative ID is returned, which we've observed in
1057 // some automated scenarios, just return it as a valid separate scope
1058 // this could cause user issues, so log a stack trace noting the call path
1059 // which resulted in this scenario.
1061 CameraId cameraId = mCameraController.getCurrentCameraId();
1063 if(cameraId == null) {
1064 Log.e(TAG, "Retrieving Camera Setting Scope with -1");
1065 return SettingsManager.getCameraSettingScope("-1");
1068 return SettingsManager.getCameraSettingScope(cameraId.getValue());
1072 public ModuleController getCurrentModuleController() {
1073 return mCurrentModule;
1077 public int getQuickSwitchToModuleId(int currentModuleIndex) {
1078 return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
1083 public SurfaceTexture getPreviewBuffer() {
1084 // TODO: implement this
1089 public void onPreviewReadyToStart() {
1090 mCameraAppUI.onPreviewReadyToStart();
1094 public void onPreviewStarted() {
1095 mCameraAppUI.onPreviewStarted();
1099 public void addPreviewAreaSizeChangedListener(
1100 PreviewStatusListener.PreviewAreaChangedListener listener) {
1101 mCameraAppUI.addPreviewAreaChangedListener(listener);
1105 public void removePreviewAreaSizeChangedListener(
1106 PreviewStatusListener.PreviewAreaChangedListener listener) {
1107 mCameraAppUI.removePreviewAreaChangedListener(listener);
1111 public void setupOneShotPreviewListener() {
1112 mCameraController.setOneShotPreviewCallback(mMainHandler,
1113 new CameraAgent.CameraPreviewDataCallback() {
1115 public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
1116 mCurrentModule.onPreviewInitialDataReceived();
1117 mCameraAppUI.onNewPreviewFrame();
1124 public void updatePreviewAspectRatio(float aspectRatio) {
1125 mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
1129 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
1130 mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
1134 public RectF getFullscreenRect() {
1135 return mCameraAppUI.getFullscreenRect();
1139 public void updatePreviewTransform(Matrix matrix) {
1140 mCameraAppUI.updatePreviewTransform(matrix);
1144 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1145 mCameraAppUI.setPreviewStatusListener(previewStatusListener);
1149 public FrameLayout getModuleLayoutRoot() {
1150 return mCameraAppUI.getModuleRootView();
1154 public void setShutterEventsListener(ShutterEventsListener listener) {
1155 // TODO: implement this
1159 public void setShutterEnabled(boolean enabled) {
1160 mCameraAppUI.setShutterButtonEnabled(enabled);
1164 public boolean isShutterEnabled() {
1165 return mCameraAppUI.isShutterButtonEnabled();
1169 public void startFlashAnimation(boolean shortFlash) {
1170 mCameraAppUI.startFlashAnimation(shortFlash);
1174 public void startPreCaptureAnimation() {
1175 // TODO: implement this
1179 public void cancelPreCaptureAnimation() {
1180 // TODO: implement this
1184 public void startPostCaptureAnimation() {
1185 // TODO: implement this
1189 public void startPostCaptureAnimation(Bitmap thumbnail) {
1190 // TODO: implement this
1194 public void cancelPostCaptureAnimation() {
1195 // TODO: implement this
1199 public OrientationManager getOrientationManager() {
1200 return mOrientationManager;
1204 public LocationManager getLocationManager() {
1205 return mLocationManager;
1209 public void lockOrientation() {
1210 if (mOrientationManager != null) {
1211 mOrientationManager.lockOrientation();
1216 public void unlockOrientation() {
1217 if (mOrientationManager != null) {
1218 mOrientationManager.unlockOrientation();
1223 * If not in filmstrip, this shows the capture indicator.
1225 private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
1226 if (mFilmstripVisible) {
1230 // Don't show capture indicator in Photo Sphere.
1231 // TODO: Don't reach into resources to figure out the current mode.
1232 final int photosphereModuleId = getApplicationContext().getResources().getInteger(
1233 R.integer.camera_mode_photosphere);
1234 if (mCurrentModeIndex == photosphereModuleId) {
1238 mMainHandler.post(new Runnable() {
1241 mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
1242 .getPeekAccessibilityString());
1243 mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
1249 public void notifyNewMedia(Uri uri) {
1250 // TODO: This method is running on the main thread. Also we should get
1251 // rid of that AsyncTask.
1253 updateStorageSpaceAndHint(null);
1254 ContentResolver cr = getContentResolver();
1255 String mimeType = cr.getType(uri);
1256 FilmstripItem newData = null;
1257 if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
1258 sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1259 newData = mVideoItemFactory.queryContentUri(uri);
1260 if (newData == null) {
1261 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1264 } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
1265 CameraUtil.broadcastNewPicture(mAppContext, uri);
1266 newData = mPhotoItemFactory.queryContentUri(uri);
1267 if (newData == null) {
1268 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1272 Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1276 // We are preloading the metadata for new video since we need the
1277 // rotation info for the thumbnail.
1278 new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
1280 protected FilmstripItem doInBackground(FilmstripItem... params) {
1281 FilmstripItem data = params[0];
1282 MetadataLoader.loadMetadata(getAndroidContext(), data);
1287 protected void onPostExecute(final FilmstripItem data) {
1288 // TODO: Figure out why sometimes the data is aleady there.
1289 mDataAdapter.addOrUpdate(data);
1291 // Legacy modules don't use CaptureSession, so we show the capture indicator when
1292 // the item was safed.
1293 if (mCurrentModule instanceof PhotoModule ||
1294 mCurrentModule instanceof VideoModule) {
1295 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1298 final Optional<Bitmap> bitmap = data.generateThumbnail(
1299 mAboveFilmstripControlLayout.getWidth(),
1300 mAboveFilmstripControlLayout.getMeasuredHeight());
1301 if (bitmap.isPresent()) {
1302 indicateCapture(bitmap.get(), 0);
1308 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
1312 public void enableKeepScreenOn(boolean enabled) {
1317 mKeepScreenOn = enabled;
1318 if (mKeepScreenOn) {
1319 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1320 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1322 keepScreenOnForAWhile();
1327 public CameraProvider getCameraProvider() {
1328 return mCameraController;
1332 public OneCameraOpener getCameraOpener() {
1333 return mOneCameraOpener;
1336 private void removeItemAt(int index) {
1337 mDataAdapter.removeAt(index);
1338 if (mDataAdapter.getTotalNumber() > 1) {
1339 showUndoDeletionBar();
1341 // If camera preview is the only view left in filmstrip,
1342 // no need to show undo bar.
1343 mPendingDeletion = true;
1345 if (mFilmstripVisible) {
1346 mCameraAppUI.getFilmstripContentPanel().animateHide();
1352 public boolean onOptionsItemSelected(MenuItem item) {
1353 // Handle presses on the action bar items
1354 switch (item.getItemId()) {
1355 case android.R.id.home:
1358 case R.id.action_details:
1359 showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
1361 case R.id.action_help_and_feedback:
1362 mResetToPreviewOnResume = false;
1363 new GoogleHelpHelper(this).launchGoogleHelp();
1366 return super.onOptionsItemSelected(item);
1370 private boolean isCaptureIntent() {
1371 if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1372 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1373 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1381 * Note: Make sure this callback is unregistered properly when the activity
1382 * is destroyed since we're otherwise leaking the Activity reference.
1384 private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
1385 = new CameraExceptionHandler.CameraExceptionCallback() {
1387 public void onCameraError(int errorCode) {
1388 // Not a fatal error. only do Log.e().
1389 Log.e(TAG, "Camera error callback. error=" + errorCode);
1392 public void onCameraException(
1393 RuntimeException ex, String commandHistory, int action, int state) {
1394 Log.e(TAG, "Camera Exception", ex);
1395 UsageStatistics.instance().cameraFailure(
1396 eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
1397 commandHistory, action, state);
1401 public void onDispatchThreadException(RuntimeException ex) {
1402 Log.e(TAG, "DispatchThread Exception", ex);
1403 UsageStatistics.instance().cameraFailure(
1404 eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
1405 null, UsageStatistics.NONE, UsageStatistics.NONE);
1408 private void onFatalError() {
1409 if (mCameraFatalError) {
1412 mCameraFatalError = true;
1414 // If the activity receives exception during onPause, just exit the app.
1415 if (mPaused && !isFinishing()) {
1416 Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
1419 mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
1425 public void onNewIntentTasks(Intent intent) {
1426 onModeSelected(getModeIndex());
1430 public void onCreateTasks(Bundle state) {
1431 Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
1432 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1433 mOnCreateTime = System.currentTimeMillis();
1434 mAppContext = getApplicationContext();
1435 mMainHandler = new MainHandler(this, getMainLooper());
1436 mLocationManager = new LocationManager(mAppContext);
1437 mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
1438 mSettingsManager = getServices().getSettingsManager();
1439 mSoundPlayer = new SoundPlayer(mAppContext);
1440 mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
1441 getServices().getMemoryManager());
1442 mFatalErrorHandler = new FatalErrorHandlerImpl(this);
1444 if (!Glide.isSetup()) {
1445 Context context = getAndroidContext();
1446 Glide.setup(new GlideBuilder(context)
1447 .setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
1448 .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
1450 Glide glide = Glide.get(context);
1452 // As a camera we will use a large amount of memory
1453 // for displaying images.
1454 glide.setMemoryCategory(MemoryCategory.HIGH);
1456 profile.mark("Glide.setup");
1458 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
1460 mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
1463 mActiveCameraDeviceTracker,
1464 ResolutionUtil.getDisplayMetrics(this));
1465 mOneCameraManager = OneCameraModule.provideOneCameraManager();
1466 } catch (OneCameraException e) {
1467 // Log error and continue start process while showing error dialog..
1468 Log.e(TAG, "Creating camera manager failed.", e);
1469 mFatalErrorHandler.onGenericCameraAccessFailure();
1471 profile.mark("OneCameraManager.get");
1474 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1475 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1476 CameraAgentFactory.CameraApi.API_1),
1477 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1478 CameraAgentFactory.CameraApi.AUTO),
1479 mActiveCameraDeviceTracker);
1480 mCameraController.setCameraExceptionHandler(
1481 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1482 } catch (AssertionError e) {
1483 Log.e(TAG, "Creating camera controller failed.", e);
1484 mFatalErrorHandler.onGenericCameraAccessFailure();
1487 // TODO: Try to move all the resources allocation to happen as soon as
1488 // possible so we can call module.init() at the earliest time.
1489 mModuleManager = new ModuleManagerImpl();
1491 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1493 AppUpgrader appUpgrader = new AppUpgrader(this);
1494 appUpgrader.upgrade(mSettingsManager);
1496 // Make sure the picture sizes are correctly cached for the current OS
1500 (new PictureSizeLoader(mAppContext)).computePictureSizes();
1501 } catch (AssertionError e) {
1502 Log.e(TAG, "Creating camera controller failed.", e);
1503 mFatalErrorHandler.onGenericCameraAccessFailure();
1505 profile.mark("computePictureSizes");
1506 Keys.setDefaults(mSettingsManager, mAppContext);
1508 mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager,
1509 getContentResolver());
1511 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1512 // We suppress this flag via theme when drawing the system preview
1513 // background, but once we create activity here, reactivate to the
1514 // default value. The default is important for L, we don't want to
1515 // change app behavior, just starting background drawable layout.
1516 if (ApiHelper.isLOrHigher()) {
1517 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1521 setContentView(R.layout.activity_main);
1522 profile.mark("setContentView()");
1523 // A window background is set in styles.xml for the system to show a
1524 // drawable background with gray color and camera icon before the
1525 // activity is created. We set the background to null here to prevent
1526 // overdraw, all views must take care of drawing backgrounds if
1527 // necessary. This call to setBackgroundDrawable must occur after
1528 // setContentView, otherwise a background may be set again from the
1530 getWindow().setBackgroundDrawable(null);
1532 mActionBar = getActionBar();
1533 // set actionbar background to 100% or 50% transparent
1534 if (ApiHelper.isLOrHigher()) {
1535 mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
1537 mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
1540 mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1541 mModeListView.init(mModuleManager.getSupportedModeIndexList());
1542 if (ApiHelper.HAS_ROTATION_ANIMATION) {
1543 setRotationAnimation();
1545 mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1547 public void onVisibilityChanged(boolean visible) {
1548 mModeListVisible = visible;
1549 mCameraAppUI.setShutterButtonImportantToA11y(!visible);
1550 updatePreviewVisibility();
1554 // Check if this is in the secure camera mode.
1555 Intent intent = getIntent();
1556 String action = intent.getAction();
1557 if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1558 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1559 mSecureCamera = true;
1561 mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1564 if (mSecureCamera) {
1565 // Change the window flags so that secure camera can show when
1567 Window win = getWindow();
1568 WindowManager.LayoutParams params = win.getAttributes();
1569 params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1570 win.setAttributes(params);
1572 // Filter for screen off so that we can finish secure camera
1573 // activity when screen is off.
1574 IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1575 registerReceiver(mShutdownReceiver, filter_screen_off);
1577 // Filter for phone unlock so that we can finish secure camera
1578 // via this UI path:
1579 // 1. from secure lock screen, user starts secure camera
1580 // 2. user presses home button
1581 // 3. user unlocks phone
1582 IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
1583 registerReceiver(mShutdownReceiver, filter_user_unlock);
1585 mCameraAppUI = new CameraAppUI(this,
1586 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1588 mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1590 mAboveFilmstripControlLayout =
1591 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1593 // Add the session listener so we can track the session progress
1595 getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1596 mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1597 mFilmstripController.setImageGap(
1598 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1599 profile.mark("Configure Camera UI");
1601 mPanoramaViewHelper = new PanoramaViewHelper(this);
1602 mPanoramaViewHelper.onCreate();
1604 ContentResolver appContentResolver = mAppContext.getContentResolver();
1605 GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
1606 mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
1607 new PhotoDataFactory());
1608 mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
1609 new VideoDataFactory());
1610 mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1611 if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1612 Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1613 mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1616 setModuleFromModeIndex(getModeIndex());
1619 mCameraAppUI.prepareModuleUI();
1620 profile.mark("Init Current Module UI");
1621 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1622 profile.mark("Init CurrentModule");
1626 mLocalImagesObserver = new FilmstripContentObserver();
1627 mLocalVideosObserver = new FilmstripContentObserver();
1629 getContentResolver().registerContentObserver(
1630 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1631 mLocalImagesObserver);
1632 getContentResolver().registerContentObserver(
1633 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1634 mLocalVideosObserver);
1636 mMemoryManager = getServices().getMemoryManager();
1638 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1641 HashMap memoryData = mMemoryManager.queryMemory();
1642 UsageStatistics.instance().reportMemoryConsumed(memoryData,
1643 MemoryQuery.REPORT_LABEL_LAUNCH);
1647 mMotionManager = getServices().getMotionManager();
1649 mFirstRunDialog = new FirstRunDialog(this,
1650 getAndroidContext(),
1654 new FirstRunDialog.FirstRunDialogListener() {
1656 public void onFirstRunStateReady() {
1657 // Run normal resume tasks.
1662 public void onFirstRunDialogCancelled() {
1663 // App isn't functional until users finish first run dialog.
1664 // We need to finish here since users hit back button during
1665 // first run dialog (b/19593942).
1670 public void onCameraAccessException() {
1671 mFatalErrorHandler.onGenericCameraAccessFailure();
1678 * Get the current mode index from the Intent or from persistent
1681 private int getModeIndex() {
1683 int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1684 int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1685 int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1686 int captureIntentIndex =
1687 getResources().getInteger(R.integer.camera_mode_capture_intent);
1688 String intentAction = getIntent().getAction();
1689 if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
1690 || MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
1691 modeIndex = videoIndex;
1692 } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
1693 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1695 modeIndex = captureIntentIndex;
1696 } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
1697 ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
1698 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
1699 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1700 Keys.KEY_CAMERA_MODULE_LAST_USED);
1702 // For upgraders who have not seen the aspect ratio selection screen,
1703 // we need to drop them back in the photo module and have them select
1705 // TODO: Move this to SettingsManager as an upgrade procedure.
1706 if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1707 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1708 modeIndex = photoIndex;
1711 // If the activity has not been started using an explicit intent,
1712 // read the module index from the last time the user changed modes
1713 modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1714 Keys.KEY_STARTUP_MODULE_INDEX);
1715 if ((modeIndex == gcamIndex &&
1716 !GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
1717 modeIndex = photoIndex;
1724 * Call this whenever the mode drawer or filmstrip change the visibility
1727 private void updatePreviewVisibility() {
1728 if (mCurrentModule == null) {
1732 int visibility = getPreviewVisibility();
1733 mCameraAppUI.onPreviewVisiblityChanged(visibility);
1734 updatePreviewRendering(visibility);
1735 mCurrentModule.onPreviewVisibilityChanged(visibility);
1738 private void updatePreviewRendering(int visibility) {
1739 if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1740 mCameraAppUI.pausePreviewRendering();
1742 mCameraAppUI.resumePreviewRendering();
1746 private int getPreviewVisibility() {
1747 if (mFilmstripCoversPreview) {
1748 return ModuleController.VISIBILITY_HIDDEN;
1749 } else if (mModeListVisible){
1750 return ModuleController.VISIBILITY_COVERED;
1752 return ModuleController.VISIBILITY_VISIBLE;
1756 private void setRotationAnimation() {
1757 int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1758 rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1759 Window win = getWindow();
1760 WindowManager.LayoutParams winParams = win.getAttributes();
1761 winParams.rotationAnimation = rotationAnimation;
1762 win.setAttributes(winParams);
1766 public void onUserInteraction() {
1767 super.onUserInteraction();
1768 if (!isFinishing()) {
1769 keepScreenOnForAWhile();
1774 public boolean dispatchTouchEvent(MotionEvent ev) {
1775 boolean result = super.dispatchTouchEvent(ev);
1776 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1777 // Real deletion is postponed until the next user interaction after
1778 // the gesture that triggers deletion. Until real deletion is
1779 // performed, users can click the undo button to bring back the
1780 // image that they chose to delete.
1781 if (mPendingDeletion && !mIsUndoingDeletion) {
1789 public void onPauseTasks() {
1790 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1791 Profile profile = mProfiler.create("CameraActivity.onPause").start();
1794 * Save the last module index after all secure camera and icon launches,
1795 * not just on mode switches.
1797 * Right now we exclude capture intents from this logic, because we also
1798 * ignore the cross-Activity recovery logic in onStart for capture intents.
1800 if (!isCaptureIntent()) {
1801 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1802 Keys.KEY_STARTUP_MODULE_INDEX,
1807 mCameraAppUI.hideCaptureIndicator();
1808 mFirstRunDialog.dismiss();
1810 // Delete photos that are pending deletion
1812 mCurrentModule.pause();
1813 mOrientationManager.pause();
1814 mPanoramaViewHelper.onPause();
1816 mLocalImagesObserver.setForegroundChangeListener(null);
1817 mLocalImagesObserver.setActivityPaused(true);
1818 mLocalVideosObserver.setActivityPaused(true);
1819 if (mPreloader != null) {
1820 mPreloader.cancelAllLoads();
1824 mMotionManager.stop();
1826 // Always stop recording location when paused. Resume will start
1827 // location recording again if the location setting is on.
1828 mLocationManager.recordLocation(false);
1830 UsageStatistics.instance().backgrounded();
1832 // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1833 // button. Let's just kill the process.
1834 if (mCameraFatalError && !isFinishing()) {
1835 Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1838 // Close the camera and wait for the operation done.
1839 Log.v(TAG, "onPause closing camera");
1840 if (mCameraController != null) {
1841 mCameraController.closeCamera(true);
1849 public void onResumeTasks() {
1852 if (!mHasCriticalPermissions) {
1853 Log.v(TAG, "Missing critical permissions.");
1856 if (!mSecureCamera) {
1857 // Show the dialog if necessary. The rest resume logic will be invoked
1858 // at the onFirstRunStateReady() callback.
1860 mFirstRunDialog.showIfNecessary();
1861 } catch (AssertionError e) {
1862 Log.e(TAG, "Creating camera controller failed.", e);
1863 mFatalErrorHandler.onGenericCameraAccessFailure();
1866 // In secure mode from lockscreen, we go straight to camera and will
1867 // show first run dialog next time user enters launcher.
1868 Log.v(TAG, "in secure mode, skipping first run dialog check");
1874 * Checks if any of the needed Android runtime permissions are missing.
1875 * If they are, then launch the permissions activity under one of the following conditions:
1876 * a) The permissions dialogs have not run yet. We will ask for permission only once.
1877 * b) If the missing permissions are critical to the app running, we will display a fatal error dialog.
1878 * Critical permissions are: camera, microphone and storage. The app cannot run without them.
1879 * Non-critical permission is location.
1881 private void checkPermissions() {
1882 if (!ApiHelper.isMOrHigher()) {
1883 Log.v(TAG, "not running on M, skipping permission checks");
1884 mHasCriticalPermissions = true;
1888 if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
1889 checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
1890 checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
1891 mHasCriticalPermissions = true;
1893 mHasCriticalPermissions = false;
1896 if ((checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
1897 !mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS)) ||
1898 !mHasCriticalPermissions) {
1899 Intent intent = new Intent(this, PermissionsActivity.class);
1900 startActivityForResult(intent, PERMISSIONS_ACTIVITY_REQUEST_CODE);
1905 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1906 super.onActivityResult(requestCode, resultCode, data);
1907 // Close the app if critical permissions are missing.
1908 if (requestCode == PERMISSIONS_ACTIVITY_REQUEST_CODE && resultCode == PERMISSIONS_RESULT_CODE_FAILED) {
1910 } else if (requestCode == PERMISSIONS_ACTIVITY_REQUEST_CODE && resultCode == PERMISSIONS_RESULT_CODE_OK) {
1911 mHasCriticalPermissions = true;
1915 private void preloadFilmstripItems() {
1916 if (mDataAdapter == null) {
1917 mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
1918 mPhotoItemFactory, mVideoItemFactory);
1919 mDataAdapter.setLocalDataListener(mFilmstripItemListener);
1920 mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1922 if (!mSecureCamera) {
1923 mFilmstripController.setDataAdapter(mDataAdapter);
1924 if (!isCaptureIntent()) {
1925 mDataAdapter.requestLoad(new Callback<Void>() {
1927 public void onCallback(Void result) {
1928 fillTemporarySessions();
1933 // Put a lock placeholder as the last image by setting its date to
1935 ImageView v = (ImageView) getLayoutInflater().inflate(
1936 R.layout.secure_album_placeholder, null);
1937 v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1938 v.setOnClickListener(new View.OnClickListener() {
1940 public void onClick(View view) {
1941 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1942 NavigationChange.InteractionCause.BUTTON);
1947 v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1948 mDataAdapter = new FixedLastProxyAdapter(
1951 new PlaceholderItem(
1953 FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
1954 v.getDrawable().getIntrinsicWidth(),
1955 v.getDrawable().getIntrinsicHeight()));
1956 // Flush out all the original data.
1957 mDataAdapter.clear();
1958 mFilmstripController.setDataAdapter(mDataAdapter);
1963 private void resume() {
1964 Profile profile = mProfiler.create("CameraActivity.resume").start();
1965 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1966 Log.v(TAG, "Build info: " + Build.DISPLAY);
1967 preloadFilmstripItems();
1968 updateStorageSpaceAndHint(null);
1970 mLastLayoutOrientation = getResources().getConfiguration().orientation;
1972 // TODO: Handle this in OrientationManager.
1974 if (Settings.System.getInt(getContentResolver(),
1975 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1976 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1977 mAutoRotateScreen = false;
1979 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1980 mAutoRotateScreen = true;
1983 // Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
1984 // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1985 // lockscreen onResume->onPause->onResume sequence.
1987 String action = getIntent().getAction();
1988 if (action == null) {
1989 source = ForegroundSource.UNKNOWN_SOURCE;
1992 case MediaStore.ACTION_IMAGE_CAPTURE:
1993 source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1995 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1996 // was UNKNOWN_SOURCE in Fishlake.
1997 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1999 case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
2000 // was UNKNOWN_SOURCE in Fishlake.
2001 source = ForegroundSource.ACTION_VIDEO_CAMERA;
2003 case MediaStore.ACTION_VIDEO_CAPTURE:
2004 source = ForegroundSource.ACTION_VIDEO_CAPTURE;
2006 case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
2007 // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
2008 source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
2010 case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
2011 source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
2013 case Intent.ACTION_MAIN:
2014 source = ForegroundSource.ACTION_MAIN;
2017 source = ForegroundSource.UNKNOWN_SOURCE;
2021 UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(),
2022 isKeyguardSecure(), isKeyguardLocked(),
2023 mStartupOnCreate, mExecutionStartNanoTime);
2025 mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
2026 if (ApiHelper.isLOrHigher()) {
2027 // hide the up affordance for L devices, it's not very Materially
2028 mActionBar.setDisplayShowHomeEnabled(false);
2031 mOrientationManager.resume();
2033 mCurrentModule.hardResetSettings(mSettingsManager);
2036 mCurrentModule.resume();
2037 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2038 NavigationChange.InteractionCause.BUTTON);
2039 setSwipingEnabled(true);
2040 profile.mark("mCurrentModule.resume");
2042 if (!mResetToPreviewOnResume) {
2043 FilmstripItem item = mDataAdapter.getItemAt(
2044 mFilmstripController.getCurrentAdapterIndex());
2046 mDataAdapter.refresh(item.getData().getUri());
2050 // The share button might be disabled to avoid double tapping.
2051 mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
2052 // Default is showing the preview, unless disabled by explicitly
2053 // starting an activity we want to return from to the filmstrip rather
2054 // than the preview.
2055 mResetToPreviewOnResume = true;
2057 if (mLocalVideosObserver.isMediaDataChangedDuringPause()
2058 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
2059 if (!mSecureCamera) {
2060 // If it's secure camera, requestLoad() should not be called
2061 // as it will load all the data.
2062 if (!mFilmstripVisible) {
2063 mDataAdapter.requestLoad(new Callback<Void>() {
2065 public void onCallback(Void result) {
2066 fillTemporarySessions();
2070 mDataAdapter.requestLoadNewPhotos();
2074 mLocalImagesObserver.setActivityPaused(false);
2075 mLocalVideosObserver.setActivityPaused(false);
2076 if (!mSecureCamera) {
2077 mLocalImagesObserver.setForegroundChangeListener(
2078 new FilmstripContentObserver.ChangeListener() {
2080 public void onChange() {
2081 mDataAdapter.requestLoadNewPhotos();
2086 keepScreenOnForAWhile();
2088 // Lights-out mode at all times.
2089 final View rootView = findViewById(R.id.activity_root_view);
2090 mLightsOutRunnable.run();
2091 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
2092 new OnSystemUiVisibilityChangeListener() {
2094 public void onSystemUiVisibilityChange(int visibility) {
2095 mMainHandler.removeCallbacks(mLightsOutRunnable);
2096 mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
2101 mPanoramaViewHelper.onResume();
2102 profile.mark("mPanoramaViewHelper.onResume()");
2104 ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
2105 // Enable location recording if the setting is on.
2106 final boolean locationRecordingEnabled =
2107 mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
2108 mLocationManager.recordLocation(locationRecordingEnabled);
2110 final int previewVisibility = getPreviewVisibility();
2111 updatePreviewRendering(previewVisibility);
2113 mMotionManager.start();
2117 private void fillTemporarySessions() {
2118 if (mSecureCamera) {
2121 // There might be sessions still in flight (processed by our service).
2122 // Make sure they're added to the filmstrip.
2123 getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
2127 public void onStartTasks() {
2128 mIsActivityRunning = true;
2129 mPanoramaViewHelper.onStart();
2132 * If we're starting after launching a different Activity (lockscreen),
2133 * we need to use the last mode used in the other Activity, and
2134 * not the old one from this Activity.
2136 * This needs to happen before CameraAppUI.resume() in order to set the
2137 * mode cover icon to the actual last mode used.
2139 * Right now we exclude capture intents from this logic.
2141 int modeIndex = getModeIndex();
2142 if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
2143 onModeSelected(modeIndex);
2146 if (mResetToPreviewOnResume) {
2147 mCameraAppUI.resume();
2148 mResetToPreviewOnResume = false;
2153 protected void onStopTasks() {
2154 mIsActivityRunning = false;
2155 mPanoramaViewHelper.onStop();
2157 mLocationManager.disconnect();
2161 public void onDestroyTasks() {
2162 if (mSecureCamera) {
2163 unregisterReceiver(mShutdownReceiver);
2166 // Ensure anything that checks for "isPaused" returns true.
2169 mSettingsManager.removeAllListeners();
2170 if (mCameraController != null) {
2171 mCameraController.removeCallbackReceiver();
2172 mCameraController.setCameraExceptionHandler(null);
2174 getContentResolver().unregisterContentObserver(mLocalImagesObserver);
2175 getContentResolver().unregisterContentObserver(mLocalVideosObserver);
2176 getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
2177 mCameraAppUI.onDestroy();
2178 mModeListView.setVisibilityChangedListener(null);
2179 mCameraController = null;
2180 mSettingsManager = null;
2181 mOrientationManager = null;
2182 mButtonManager = null;
2183 mSoundPlayer.release();
2184 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
2185 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
2189 public void onConfigurationChanged(Configuration config) {
2190 super.onConfigurationChanged(config);
2191 Log.v(TAG, "onConfigurationChanged");
2192 if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
2196 if (mLastLayoutOrientation != config.orientation) {
2197 mLastLayoutOrientation = config.orientation;
2198 mCurrentModule.onLayoutOrientationChanged(
2199 mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
2204 public boolean onKeyDown(int keyCode, KeyEvent event) {
2205 if (!mFilmstripVisible) {
2206 if (mCurrentModule.onKeyDown(keyCode, event)) {
2209 // Prevent software keyboard or voice search from showing up.
2210 if (keyCode == KeyEvent.KEYCODE_SEARCH
2211 || keyCode == KeyEvent.KEYCODE_MENU) {
2212 if (event.isLongPress()) {
2218 return super.onKeyDown(keyCode, event);
2222 public boolean onKeyUp(int keyCode, KeyEvent event) {
2223 if (!mFilmstripVisible) {
2224 // If a module is in the middle of capture, it should
2225 // consume the key event.
2226 if (mCurrentModule.onKeyUp(keyCode, event)) {
2228 } else if (keyCode == KeyEvent.KEYCODE_MENU
2229 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2230 // Let the mode list view consume the event.
2231 mCameraAppUI.openModeList();
2233 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2234 mCameraAppUI.showFilmstrip();
2238 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
2239 mFilmstripController.goToNextItem();
2241 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
2242 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
2243 if (!wentToPrevious) {
2244 // at beginning of filmstrip, hide and go back to preview
2245 mCameraAppUI.hideFilmstrip();
2250 return super.onKeyUp(keyCode, event);
2254 public void onBackPressed() {
2255 if (!mCameraAppUI.onBackPressed()) {
2256 if (!mCurrentModule.onBackPressed()) {
2257 super.onBackPressed();
2263 public boolean isAutoRotateScreen() {
2264 // TODO: Move to OrientationManager.
2265 return mAutoRotateScreen;
2269 public boolean onCreateOptionsMenu(Menu menu) {
2270 MenuInflater inflater = getMenuInflater();
2271 inflater.inflate(R.menu.filmstrip_menu, menu);
2272 mActionBarMenu = menu;
2274 // add a button for launching the gallery
2275 if (mGalleryIntent != null) {
2276 CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2277 if (appName != null) {
2278 MenuItem menuItem = menu.add(appName);
2279 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2280 menuItem.setIntent(mGalleryIntent);
2282 Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2283 if (galleryLogo != null) {
2284 menuItem.setIcon(galleryLogo);
2289 return super.onCreateOptionsMenu(menu);
2293 public boolean onPrepareOptionsMenu(Menu menu) {
2294 if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2295 // Compatibility pre-L: launching new activities right above
2296 // lockscreen does not reliably work, only show help if not secure
2297 menu.removeItem(R.id.action_help_and_feedback);
2300 return super.onPrepareOptionsMenu(menu);
2303 protected long getStorageSpaceBytes() {
2304 synchronized (mStorageSpaceLock) {
2305 return mStorageSpaceBytes;
2309 protected interface OnStorageUpdateDoneListener {
2310 public void onStorageUpdateDone(long bytes);
2313 protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2315 * We execute disk operations on a background thread in order to
2316 * free up the UI thread. Synchronizing on the lock below ensures
2317 * that when getStorageSpaceBytes is called, the main thread waits
2318 * until this method has completed.
2320 * However, .execute() does not ensure this execution block will be
2321 * run right away (.execute() schedules this AsyncTask for sometime
2322 * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2323 * tries to execute the task in parellel with other AsyncTasks, but
2324 * there's still no guarantee).
2325 * e.g. don't call this then immediately call getStorageSpaceBytes().
2326 * Instead, pass in an OnStorageUpdateDoneListener.
2328 (new AsyncTask<Void, Void, Long>() {
2330 protected Long doInBackground(Void ... arg) {
2331 synchronized (mStorageSpaceLock) {
2332 mStorageSpaceBytes = Storage.getAvailableSpace();
2333 return mStorageSpaceBytes;
2338 protected void onPostExecute(Long bytes) {
2339 updateStorageHint(bytes);
2340 // This callback returns after I/O to check disk, so we could be
2341 // pausing and shutting down. If so, don't bother invoking.
2342 if (callback != null && !mPaused) {
2343 callback.onStorageUpdateDone(bytes);
2345 Log.v(TAG, "ignoring storage callback after activity pause");
2348 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2351 protected void updateStorageHint(long storageSpace) {
2352 if (!mIsActivityRunning) {
2356 String message = null;
2357 if (storageSpace == Storage.UNAVAILABLE) {
2358 message = getString(R.string.no_storage);
2359 } else if (storageSpace == Storage.PREPARING) {
2360 message = getString(R.string.preparing_sd);
2361 } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2362 message = getString(R.string.access_sd_fail);
2363 } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2364 message = getString(R.string.spaceIsLow_content);
2367 if (message != null) {
2368 Log.w(TAG, "Storage warning: " + message);
2369 if (mStorageHint == null) {
2370 mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
2372 mStorageHint.setText(message);
2374 mStorageHint.show();
2375 UsageStatistics.instance().storageWarning(storageSpace);
2377 // Disable all user interactions,
2378 mCameraAppUI.setDisableAllUserInteractions(true);
2379 } else if (mStorageHint != null) {
2380 mStorageHint.cancel();
2381 mStorageHint = null;
2383 // Re-enable all user interactions.
2384 mCameraAppUI.setDisableAllUserInteractions(false);
2388 protected void setResultEx(int resultCode) {
2389 mResultCodeForTesting = resultCode;
2390 setResult(resultCode);
2393 protected void setResultEx(int resultCode, Intent data) {
2394 mResultCodeForTesting = resultCode;
2395 mResultDataForTesting = data;
2396 setResult(resultCode, data);
2399 public int getResultCode() {
2400 return mResultCodeForTesting;
2403 public Intent getResultData() {
2404 return mResultDataForTesting;
2407 public boolean isSecureCamera() {
2408 return mSecureCamera;
2412 public boolean isPaused() {
2417 public int getPreferredChildModeIndex(int modeIndex) {
2418 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2419 boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2420 if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
2421 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2428 public void onModeSelected(int modeIndex) {
2429 if (mCurrentModeIndex == modeIndex) {
2433 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2434 // Record last used camera mode for quick switching
2435 if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2436 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2437 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2438 Keys.KEY_CAMERA_MODULE_LAST_USED,
2442 closeModule(mCurrentModule);
2444 // Select the correct module index from the mode switcher index.
2445 modeIndex = getPreferredChildModeIndex(modeIndex);
2446 setModuleFromModeIndex(modeIndex);
2448 mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2449 mCameraAppUI.addShutterListener(mCurrentModule);
2450 openModule(mCurrentModule);
2451 // Store the module index so we can use it the next time the Camera
2453 mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2454 Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2458 * Shows the settings dialog.
2461 public void onSettingsSelected() {
2462 UsageStatistics.instance().controlUsed(
2463 eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2464 Intent intent = new Intent(this, CameraSettingsActivity.class);
2465 startActivity(intent);
2469 public void freezeScreenUntilPreviewReady() {
2470 mCameraAppUI.freezeScreenUntilPreviewReady();
2474 public int getModuleId(int modeIndex) {
2475 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2476 if (agent == null) {
2479 return agent.getModuleId();
2483 * Sets the mCurrentModuleIndex, creates a new module instance for the given
2484 * index an sets it as mCurrentModule.
2486 private void setModuleFromModeIndex(int modeIndex) {
2487 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2488 if (agent == null) {
2491 if (!agent.requestAppForCamera()) {
2492 mCameraController.closeCamera(true);
2494 mCurrentModeIndex = agent.getModuleId();
2495 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2499 public SettingsManager getSettingsManager() {
2500 return mSettingsManager;
2504 public ResolutionSetting getResolutionSetting() {
2505 return mResolutionSetting;
2509 public CameraServices getServices() {
2510 return CameraServicesImpl.instance();
2514 public FatalErrorHandler getFatalErrorHandler() {
2515 return mFatalErrorHandler;
2518 public List<String> getSupportedModeNames() {
2519 List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2520 List<String> supported = new ArrayList<String>();
2522 for (Integer modeIndex : indices) {
2523 String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2524 if (name != null && !name.equals("")) {
2525 supported.add(name);
2532 public ButtonManager getButtonManager() {
2533 if (mButtonManager == null) {
2534 mButtonManager = new ButtonManager(this);
2536 return mButtonManager;
2540 public SoundPlayer getSoundPlayer() {
2541 return mSoundPlayer;
2545 * Launches an ACTION_EDIT intent for the given local data item. If
2546 * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2547 * the user start either the tiny planet editor or another photo editor.
2549 * @param data The data item to edit.
2551 public void launchEditor(FilmstripItem data) {
2552 Intent intent = new Intent(Intent.ACTION_EDIT)
2553 .setDataAndType(data.getData().getUri(), data.getData().getMimeType())
2554 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2556 launchActivityByIntent(intent);
2557 } catch (ActivityNotFoundException e) {
2558 final String msgEditWith = getResources().getString(R.string.edit_with);
2559 launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2564 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2565 super.onCreateContextMenu(menu, v, menuInfo);
2567 MenuInflater inflater = getMenuInflater();
2568 inflater.inflate(R.menu.filmstrip_context_menu, menu);
2572 public boolean onContextItemSelected(MenuItem item) {
2573 switch (item.getItemId()) {
2574 case R.id.tiny_planet_editor:
2575 mMyFilmstripBottomControlListener.onTinyPlanet();
2577 case R.id.photo_editor:
2578 mMyFilmstripBottomControlListener.onEdit();
2585 * Launch the tiny planet editor.
2587 * @param data The data must be a 360 degree stereographically mapped
2588 * panoramic image. It will not be modified, instead a new item
2589 * with the result will be added to the filmstrip.
2591 public void launchTinyPlanetEditor(FilmstripItem data) {
2592 TinyPlanetFragment fragment = new TinyPlanetFragment();
2593 Bundle bundle = new Bundle();
2594 bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
2595 bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
2596 fragment.setArguments(bundle);
2597 fragment.show(getFragmentManager(), "tiny_planet");
2601 * Returns what UI mode (capture mode or filmstrip) we are in.
2602 * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2604 private int currentUserInterfaceMode() {
2605 int mode = NavigationChange.Mode.UNKNOWN_MODE;
2606 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2607 mode = NavigationChange.Mode.PHOTO_CAPTURE;
2609 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2610 mode = NavigationChange.Mode.VIDEO_CAPTURE;
2612 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2613 mode = NavigationChange.Mode.LENS_BLUR;
2615 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2616 mode = NavigationChange.Mode.HDR_PLUS;
2618 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2619 mode = NavigationChange.Mode.PHOTO_SPHERE;
2621 if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2622 mode = NavigationChange.Mode.PANORAMA;
2624 if (mFilmstripVisible) {
2625 mode = NavigationChange.Mode.FILMSTRIP;
2630 private void openModule(CameraModule module) {
2631 module.init(this, isSecureCamera(), isCaptureIntent());
2632 module.hardResetSettings(mSettingsManager);
2633 // Hide accessibility zoom UI by default. Modules will enable it themselves if required.
2634 getCameraAppUI().hideAccessibilityZoomUI();
2637 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2638 NavigationChange.InteractionCause.BUTTON);
2639 updatePreviewVisibility();
2643 private void closeModule(CameraModule module) {
2645 mCameraAppUI.clearModuleUI();
2648 private void performDeletion() {
2649 if (!mPendingDeletion) {
2652 hideUndoDeletionBar(false);
2653 mDataAdapter.executeDeletion();
2656 public void showUndoDeletionBar() {
2657 if (mPendingDeletion) {
2660 Log.v(TAG, "showing undo bar");
2661 mPendingDeletion = true;
2662 if (mUndoDeletionBar == null) {
2663 ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2664 mAboveFilmstripControlLayout, true);
2665 mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2666 View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2667 button.setOnClickListener(new View.OnClickListener() {
2669 public void onClick(View view) {
2670 mDataAdapter.undoDeletion();
2671 // Fix for b/21666018: When undoing a delete in Fullscreen
2673 // back to the filmstrip to force a refresh.
2674 if (mFilmstripController.inFullScreen()) {
2675 mFilmstripController.goToFilmstrip();
2677 hideUndoDeletionBar(true);
2680 // Setting undo bar clickable to avoid touch events going through
2681 // the bar to the buttons (eg. edit button, etc) underneath the bar.
2682 mUndoDeletionBar.setClickable(true);
2683 // When there is user interaction going on with the undo button, we
2684 // do not want to hide the undo bar.
2685 button.setOnTouchListener(new View.OnTouchListener() {
2687 public boolean onTouch(View v, MotionEvent event) {
2688 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2689 mIsUndoingDeletion = true;
2690 } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2691 mIsUndoingDeletion = false;
2697 mUndoDeletionBar.setAlpha(0f);
2698 mUndoDeletionBar.setVisibility(View.VISIBLE);
2699 mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2702 private void hideUndoDeletionBar(boolean withAnimation) {
2703 Log.v(TAG, "Hiding undo deletion bar");
2704 mPendingDeletion = false;
2705 if (mUndoDeletionBar != null) {
2706 if (withAnimation) {
2707 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2708 .setListener(new Animator.AnimatorListener() {
2710 public void onAnimationStart(Animator animation) {
2715 public void onAnimationEnd(Animator animation) {
2716 mUndoDeletionBar.setVisibility(View.GONE);
2720 public void onAnimationCancel(Animator animation) {
2725 public void onAnimationRepeat(Animator animation) {
2730 mUndoDeletionBar.setVisibility(View.GONE);
2736 * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2739 * @param enable {@code true} to enable swipe.
2741 public void setSwipingEnabled(boolean enable) {
2742 // TODO: Bring back the functionality.
2743 if (isCaptureIntent()) {
2744 // lockPreview(true);
2746 // lockPreview(!enable);
2750 // Accessor methods for getting latency times used in performance testing
2751 public long getFirstPreviewTime() {
2752 if (mCurrentModule instanceof PhotoModule) {
2753 long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2754 if (coverHiddenTime != -1) {
2755 return coverHiddenTime - mOnCreateTime;
2761 public long getAutoFocusTime() {
2762 return (mCurrentModule instanceof PhotoModule) ?
2763 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2766 public long getShutterLag() {
2767 return (mCurrentModule instanceof PhotoModule) ?
2768 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2771 public long getShutterToPictureDisplayedTime() {
2772 return (mCurrentModule instanceof PhotoModule) ?
2773 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2776 public long getPictureDisplayedToJpegCallbackTime() {
2777 return (mCurrentModule instanceof PhotoModule) ?
2778 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2781 public long getJpegCallbackFinishTime() {
2782 return (mCurrentModule instanceof PhotoModule) ?
2783 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2786 public long getCaptureStartTime() {
2787 return (mCurrentModule instanceof PhotoModule) ?
2788 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2791 public boolean isRecording() {
2792 return (mCurrentModule instanceof VideoModule) ?
2793 ((VideoModule) mCurrentModule).isRecording() : false;
2796 public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2797 return mCameraController;
2800 // For debugging purposes only.
2801 public CameraModule getCurrentModule() {
2802 return mCurrentModule;
2806 public void showTutorial(AbstractTutorialOverlay tutorial) {
2807 mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2811 public void finishActivityWithIntentCompleted(Intent resultIntent) {
2812 finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
2816 public void finishActivityWithIntentCanceled() {
2817 finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
2820 private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
2821 mResultCodeForTesting = resultCode;
2822 mResultDataForTesting = resultIntent;
2823 setResult(resultCode, resultIntent);
2827 private void keepScreenOnForAWhile() {
2828 if (mKeepScreenOn) {
2831 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2832 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2833 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2836 private void resetScreenOn() {
2837 mKeepScreenOn = false;
2838 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2839 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2843 * @return {@code true} if the Gallery is launched successfully.
2845 private boolean startGallery() {
2846 if (mGalleryIntent == null) {
2850 UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2851 NavigationChange.InteractionCause.BUTTON);
2852 Intent startGalleryIntent = new Intent(mGalleryIntent);
2853 int currentIndex = mFilmstripController.getCurrentAdapterIndex();
2854 FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
2855 if (currentFilmstripItem != null) {
2856 GalleryHelper.setContentUri(startGalleryIntent,
2857 currentFilmstripItem.getData().getUri());
2859 launchActivityByIntent(startGalleryIntent);
2860 } catch (ActivityNotFoundException e) {
2861 Log.w(TAG, "Failed to launch gallery activity, closing");
2866 private void setNfcBeamPushUriFromData(FilmstripItem data) {
2867 final Uri uri = data.getData().getUri();
2868 if (uri != Uri.EMPTY) {
2869 mNfcPushUris[0] = uri;
2871 mNfcPushUris[0] = null;
2876 * Updates the visibility of the filmstrip bottom controls and action bar.
2878 private void updateUiByData(final int index) {
2879 final FilmstripItem currentData = mDataAdapter.getItemAt(index);
2880 if (currentData == null) {
2881 Log.w(TAG, "Current data ID not found.");
2882 hideSessionProgress();
2885 updateActionBarMenu(currentData);
2887 /* Bottom controls. */
2888 updateBottomControlsByData(currentData);
2890 if (isSecureCamera()) {
2891 // We cannot show buttons in secure camera since go to other
2892 // activities might create a security hole.
2893 mCameraAppUI.getFilmstripBottomControls().hideControls();
2897 setNfcBeamPushUriFromData(currentData);
2899 if (!mDataAdapter.isMetadataUpdatedAt(index)) {
2900 mDataAdapter.updateMetadataAt(index);
2905 * Updates the bottom controls based on the data.
2907 private void updateBottomControlsByData(final FilmstripItem currentData) {
2909 final CameraAppUI.BottomPanel filmstripBottomPanel =
2910 mCameraAppUI.getFilmstripBottomControls();
2911 filmstripBottomPanel.showControls();
2912 filmstripBottomPanel.setEditButtonVisibility(
2913 currentData.getAttributes().canEdit());
2914 filmstripBottomPanel.setShareButtonVisibility(
2915 currentData.getAttributes().canShare());
2916 filmstripBottomPanel.setDeleteButtonVisibility(
2917 currentData.getAttributes().canDelete());
2921 Uri contentUri = currentData.getData().getUri();
2922 CaptureSessionManager sessionManager = getServices()
2923 .getCaptureSessionManager();
2925 if (sessionManager.hasErrorMessage(contentUri)) {
2926 showProcessError(sessionManager.getErrorMessageId(contentUri));
2928 filmstripBottomPanel.hideProgressError();
2929 CaptureSession session = sessionManager.getSession(contentUri);
2931 if (session != null) {
2932 int sessionProgress = session.getProgress();
2934 if (sessionProgress < 0) {
2935 hideSessionProgress();
2937 int progressMessageId = session.getProgressMessageId();
2938 showSessionProgress(progressMessageId);
2939 updateSessionProgress(sessionProgress);
2942 hideSessionProgress();
2948 // We need to add this to a separate DB.
2949 final int viewButtonVisibility;
2950 if (currentData.getMetadata().isUsePanoramaViewer()) {
2951 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2952 } else if (currentData.getMetadata().isHasRgbzData()) {
2953 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2955 viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2958 filmstripBottomPanel.setTinyPlanetEnabled(
2959 currentData.getMetadata().isPanorama360());
2960 filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2963 private void showDetailsDialog(int index) {
2964 final FilmstripItem data = mDataAdapter.getItemAt(index);
2968 Optional<MediaDetails> details = data.getMediaDetails();
2969 if (!details.isPresent()) {
2972 Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
2973 detailDialog.show();
2974 UsageStatistics.instance().mediaInteraction(
2975 fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
2976 NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
2980 * Show or hide action bar items depending on current data type.
2982 private void updateActionBarMenu(FilmstripItem data) {
2983 if (mActionBarMenu == null) {
2987 MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2988 if (detailsMenuItem == null) {
2992 boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
2993 detailsMenuItem.setVisible(showDetails);