OSDN Git Service

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