OSDN Git Service

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