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