OSDN Git Service

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