OSDN Git Service

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