OSDN Git Service

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