OSDN Git Service

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