OSDN Git Service

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