OSDN Git Service

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