OSDN Git Service

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