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