OSDN Git Service

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