OSDN Git Service

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