OSDN Git Service

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