OSDN Git Service

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