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