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