OSDN Git Service

add filmstrip DPAD capabilities
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / CameraActivity.java
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.camera;
18
19 import android.animation.Animator;
20 import android.annotation.TargetApi;
21 import android.app.ActionBar;
22 import android.app.Activity;
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.SharedPreferences;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.PackageManager;
34 import android.content.res.Configuration;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.graphics.Matrix;
38 import android.graphics.Point;
39 import android.graphics.SurfaceTexture;
40 import android.graphics.drawable.ColorDrawable;
41 import android.graphics.drawable.Drawable;
42 import android.net.Uri;
43 import android.nfc.NfcAdapter;
44 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
45 import android.nfc.NfcEvent;
46 import android.os.AsyncTask;
47 import android.os.Build;
48 import android.os.Bundle;
49 import android.os.Handler;
50 import android.os.HandlerThread;
51 import android.os.Looper;
52 import android.os.Message;
53 import android.preference.PreferenceManager;
54 import android.provider.MediaStore;
55 import android.provider.Settings;
56 import android.util.CameraPerformanceTracker;
57 import android.util.Log;
58 import android.view.ContextMenu;
59 import android.view.ContextMenu.ContextMenuInfo;
60 import android.view.KeyEvent;
61 import android.view.Menu;
62 import android.view.MenuInflater;
63 import android.view.MenuItem;
64 import android.view.MotionEvent;
65 import android.view.View;
66 import android.view.ViewGroup;
67 import android.view.Window;
68 import android.view.WindowManager;
69 import android.widget.FrameLayout;
70 import android.widget.ImageView;
71 import android.widget.ShareActionProvider;
72
73 import com.android.camera.app.AppController;
74 import com.android.camera.app.CameraAppUI;
75 import com.android.camera.app.CameraController;
76 import com.android.camera.app.CameraManager;
77 import com.android.camera.app.CameraManagerFactory;
78 import com.android.camera.app.CameraProvider;
79 import com.android.camera.app.CameraServices;
80 import com.android.camera.app.LocationManager;
81 import com.android.camera.app.ModuleManagerImpl;
82 import com.android.camera.app.OrientationManager;
83 import com.android.camera.app.OrientationManagerImpl;
84 import com.android.camera.data.CameraDataAdapter;
85 import com.android.camera.data.FixedLastDataAdapter;
86 import com.android.camera.data.LocalData;
87 import com.android.camera.data.LocalDataAdapter;
88 import com.android.camera.data.LocalDataUtil;
89 import com.android.camera.data.LocalMediaData;
90 import com.android.camera.data.LocalMediaObserver;
91 import com.android.camera.data.LocalSessionData;
92 import com.android.camera.data.MediaDetails;
93 import com.android.camera.data.PanoramaMetadataLoader;
94 import com.android.camera.data.RgbzMetadataLoader;
95 import com.android.camera.data.SimpleViewData;
96 import com.android.camera.filmstrip.FilmstripContentPanel;
97 import com.android.camera.filmstrip.FilmstripController;
98 import com.android.camera.hardware.HardwareSpec;
99 import com.android.camera.hardware.HardwareSpecImpl;
100 import com.android.camera.module.ModuleController;
101 import com.android.camera.module.ModulesInfo;
102 import com.android.camera.session.CaptureSession;
103 import com.android.camera.session.CaptureSessionManager;
104 import com.android.camera.session.CaptureSessionManager.SessionListener;
105 import com.android.camera.settings.CameraSettingsActivity;
106 import com.android.camera.settings.SettingsManager;
107 import com.android.camera.settings.SettingsManager.SettingsCapabilities;
108 import com.android.camera.settings.SettingsUtil;
109 import com.android.camera.tinyplanet.TinyPlanetFragment;
110 import com.android.camera.ui.AbstractTutorialOverlay;
111 import com.android.camera.ui.DetailsDialog;
112 import com.android.camera.ui.MainActivityLayout;
113 import com.android.camera.ui.ModeListView;
114 import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
115 import com.android.camera.ui.PreviewStatusListener;
116 import com.android.camera.util.ApiHelper;
117 import com.android.camera.util.Callback;
118 import com.android.camera.util.CameraUtil;
119 import com.android.camera.util.FeedbackHelper;
120 import com.android.camera.util.GalleryHelper;
121 import com.android.camera.util.GcamHelper;
122 import com.android.camera.util.IntentHelper;
123 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
124 import com.android.camera.util.ReleaseDialogHelper;
125 import com.android.camera.util.UsageStatistics;
126 import com.android.camera.widget.FilmstripView;
127 import com.android.camera.widget.Preloader;
128 import com.android.camera2.R;
129 import com.google.common.logging.eventprotos;
130 import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
131 import com.google.common.logging.eventprotos.NavigationChange;
132
133 import java.io.File;
134 import java.io.FileInputStream;
135 import java.io.FileNotFoundException;
136 import java.lang.ref.WeakReference;
137 import java.util.ArrayList;
138 import java.util.List;
139
140 public class CameraActivity extends Activity
141         implements AppController, CameraManager.CameraOpenCallback,
142         ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
143         OrientationManager.OnOrientationChangeListener {
144
145     private static final String TAG = "CameraActivity";
146
147     private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
148             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
149     public static final String ACTION_IMAGE_CAPTURE_SECURE =
150             "android.media.action.IMAGE_CAPTURE_SECURE";
151
152     // The intent extra for camera from secure lock screen. True if the gallery
153     // should only show newly captured pictures. sSecureAlbumId does not
154     // increment. This is used when switching between camera, camcorder, and
155     // panorama. If the extra is not set, it is in the normal camera mode.
156     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
157
158     /**
159      * Request code from an activity we started that indicated that we do not
160      * want to reset the view to the preview in onResume.
161      */
162     public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
163
164     public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
165
166     private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
167     private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
168     private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs.
169     /** Load metadata for 10 items ahead of our current. */
170     private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
171
172     /** Should be used wherever a context is needed. */
173     private Context mAppContext;
174
175     /**
176      * Whether onResume should reset the view to the preview.
177      */
178     private boolean mResetToPreviewOnResume = true;
179
180     /**
181      * This data adapter is used by FilmStripView.
182      */
183     private LocalDataAdapter mDataAdapter;
184
185     /**
186      * TODO: This should be moved to the app level.
187      */
188     private SettingsManager mSettingsManager;
189
190     private ModeListView mModeListView;
191     private boolean mModeListVisible = false;
192     private int mCurrentModeIndex;
193     private CameraModule mCurrentModule;
194     private ModuleManagerImpl mModuleManager;
195     private FrameLayout mAboveFilmstripControlLayout;
196     private FilmstripController mFilmstripController;
197     private boolean mFilmstripVisible;
198     /** Whether the filmstrip fully covers the preview. */
199     private boolean mFilmstripCoversPreview = false;
200     private int mResultCodeForTesting;
201     private Intent mResultDataForTesting;
202     private OnScreenHint mStorageHint;
203     private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
204     private boolean mAutoRotateScreen;
205     private boolean mSecureCamera;
206     private int mLastRawOrientation;
207     private OrientationManagerImpl mOrientationManager;
208     private LocationManager mLocationManager;
209     private ButtonManager mButtonManager;
210     private Handler mMainHandler;
211     private PanoramaViewHelper mPanoramaViewHelper;
212     private ActionBar mActionBar;
213     private ViewGroup mUndoDeletionBar;
214     private boolean mIsUndoingDeletion = false;
215
216     private final Uri[] mNfcPushUris = new Uri[1];
217
218     private LocalMediaObserver mLocalImagesObserver;
219     private LocalMediaObserver mLocalVideosObserver;
220
221     private boolean mPendingDeletion = false;
222
223     private CameraController mCameraController;
224     private boolean mPaused;
225     private CameraAppUI mCameraAppUI;
226
227     private PeekAnimationHandler mPeekAnimationHandler;
228     private HandlerThread mPeekAnimationThread;
229
230     private FeedbackHelper mFeedbackHelper;
231
232     private Intent mGalleryIntent;
233     private long mOnCreateTime;
234
235     private Menu mActionBarMenu;
236     private Preloader<Integer, AsyncTask> mPreloader;
237
238     @Override
239     public CameraAppUI getCameraAppUI() {
240         return mCameraAppUI;
241     }
242
243     // close activity when screen turns off
244     private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
245         @Override
246         public void onReceive(Context context, Intent intent) {
247             finish();
248         }
249     };
250
251     /**
252      * Whether the screen is kept turned on.
253      */
254     private boolean mKeepScreenOn;
255     private int mLastLayoutOrientation;
256     private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
257             new CameraAppUI.BottomPanel.Listener() {
258
259                 /**
260                  * If the current photo is a photo sphere, this will launch the
261                  * Photo Sphere panorama viewer.
262                  */
263                 @Override
264                 public void onExternalViewer() {
265                     if (mPanoramaViewHelper == null) {
266                         return;
267                     }
268                     final LocalData data = getCurrentLocalData();
269                     if (data == null) {
270                         return;
271                     }
272                     final Uri contentUri = data.getUri();
273                     if (contentUri == Uri.EMPTY) {
274                         return;
275                     }
276
277                     if (PanoramaMetadataLoader.isPanoramaAndUseViewer(data)) {
278                         mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
279                     } else if (RgbzMetadataLoader.hasRGBZData(data)) {
280                         mPanoramaViewHelper.showRgbz(contentUri);
281                     }
282                 }
283
284                 @Override
285                 public void onEdit() {
286                     LocalData data = getCurrentLocalData();
287                     if (data == null) {
288                         return;
289                     }
290                     launchEditor(data);
291                 }
292
293                 @Override
294                 public void onTinyPlanet() {
295                     LocalData data = getCurrentLocalData();
296                     if (data == null) {
297                         return;
298                     }
299                     launchTinyPlanetEditor(data);
300                 }
301
302                 @Override
303                 public void onDelete() {
304                     final int currentDataId = getCurrentDataId();
305                     UsageStatistics.photoInteraction(
306                             UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
307                             eventprotos.CameraEvent.InteractionType.DELETE,
308                             InteractionCause.BUTTON);
309                     removeData(currentDataId);
310                 }
311
312                 @Override
313                 public void onShare() {
314                     final LocalData data = getCurrentLocalData();
315
316                     // If applicable, show release information before this item
317                     // is shared.
318                     if (PanoramaMetadataLoader.isPanorama(data)
319                             || RgbzMetadataLoader.hasRGBZData(data)) {
320                         ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
321                                 new Callback<Void>() {
322                                     @Override
323                                     public void onCallback(Void result) {
324                                         share(data);
325                                     }
326                                 });
327                     } else {
328                         share(data);
329                     }
330                 }
331
332                 private void share(LocalData data) {
333                     Intent shareIntent = getShareIntentByData(data);
334                     if (shareIntent != null) {
335                         try {
336                             launchActivityByIntent(shareIntent);
337                             mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
338                         } catch (ActivityNotFoundException ex) {
339                             // Nothing.
340                         }
341                     }
342                 }
343
344                 private int getCurrentDataId() {
345                     return mFilmstripController.getCurrentId();
346                 }
347
348                 private LocalData getCurrentLocalData() {
349                     return mDataAdapter.getLocalData(getCurrentDataId());
350                 }
351
352                 /**
353                  * Sets up the share intent and NFC properly according to the
354                  * data.
355                  *
356                  * @param data The data to be shared.
357                  */
358                 private Intent getShareIntentByData(final LocalData data) {
359                     Intent intent = null;
360                     final Uri contentUri = data.getUri();
361                     if (PanoramaMetadataLoader.isPanorama360(data) &&
362                             data.getUri() != Uri.EMPTY) {
363                         intent = new Intent(Intent.ACTION_SEND);
364                         intent.setType("application/vnd.google.panorama360+jpg");
365                         intent.putExtra(Intent.EXTRA_STREAM, contentUri);
366                     } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
367                         final String mimeType = data.getMimeType();
368                         intent = getShareIntentFromType(mimeType);
369                         if (intent != null) {
370                             intent.putExtra(Intent.EXTRA_STREAM, contentUri);
371                             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
372                         }
373                         intent = Intent.createChooser(intent, null);
374                     }
375                     return intent;
376                 }
377
378                 /**
379                  * Get the share intent according to the mimeType
380                  *
381                  * @param mimeType The mimeType of current data.
382                  * @return the video/image's ShareIntent or null if mimeType is
383                  *         invalid.
384                  */
385                 private Intent getShareIntentFromType(String mimeType) {
386                     // Lazily create the intent object.
387                     Intent intent = new Intent(Intent.ACTION_SEND);
388                     if (mimeType.startsWith("video/")) {
389                         intent.setType("video/*");
390                     } else {
391                         if (mimeType.startsWith("image/")) {
392                             intent.setType("image/*");
393                         } else {
394                             Log.w(TAG, "unsupported mimeType " + mimeType);
395                         }
396                     }
397                     return intent;
398                 }
399
400                 @Override
401                 public void onProgressErrorClicked() {
402                     LocalData data = getCurrentLocalData();
403                     getServices().getCaptureSessionManager().removeErrorMessage(
404                             data.getUri());
405                     updateBottomControlsByData(data);
406                 }
407             };
408
409     private ComboPreferences mPreferences;
410
411     @Override
412     public void onCameraOpened(CameraManager.CameraProxy camera) {
413         /**
414          * The current UI requires that the flash option visibility in front-facing
415          * camera be
416          *   * disabled if back facing camera supports flash
417          *   * hidden if back facing camera does not support flash
418          * We save whether back facing camera supports flash because we cannot get
419          * this in front facing camera without a camera switch.
420          *
421          * If this preference is cleared, we also need to clear the camera facing
422          * setting so we default to opening the camera in back facing camera, and
423          * can save this flash support value again.
424          */
425         if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
426             HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
427             mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
428                 hardware.isFlashSupported());
429         }
430
431         if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
432             // We shouldn't be here. Just close the camera and leave.
433             camera.release(false);
434             throw new IllegalStateException("Camera opened but the module shouldn't be " +
435                     "requesting");
436         }
437         if (mCurrentModule != null) {
438             SettingsCapabilities capabilities =
439                     SettingsUtil.getSettingsCapabilities(camera);
440             mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
441             mCurrentModule.onCameraAvailable(camera);
442         }
443         mCameraAppUI.onChangeCamera();
444     }
445
446     @Override
447     public void onCameraDisabled(int cameraId) {
448         UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY);
449
450         CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
451     }
452
453     @Override
454     public void onDeviceOpenFailure(int cameraId) {
455         UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
456
457         CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
458     }
459
460     @Override
461     public void onDeviceOpenedAlready(int cameraId) {
462         CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
463     }
464
465     @Override
466     public void onReconnectionFailure(CameraManager mgr) {
467         UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
468
469         CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
470     }
471
472     private static class MainHandler extends Handler {
473         final WeakReference<CameraActivity> mActivity;
474
475         public MainHandler(CameraActivity activity, Looper looper) {
476             super(looper);
477             mActivity = new WeakReference<CameraActivity>(activity);
478         }
479
480         @Override
481         public void handleMessage(Message msg) {
482             CameraActivity activity = mActivity.get();
483             if (activity == null) {
484                 return;
485             }
486             switch (msg.what) {
487
488                 case MSG_CLEAR_SCREEN_ON_FLAG: {
489                     if (!activity.mPaused) {
490                         activity.getWindow().clearFlags(
491                                 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
492                     }
493                     break;
494                 }
495             }
496         }
497     }
498
499     private String fileNameFromDataID(int dataID) {
500         final LocalData localData = mDataAdapter.getLocalData(dataID);
501
502         File localFile = new File(localData.getPath());
503         return localFile.getName();
504     }
505
506     private final FilmstripContentPanel.Listener mFilmstripListener =
507             new FilmstripContentPanel.Listener() {
508
509                 @Override
510                 public void onSwipeOut() {
511                     UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
512                             eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
513                 }
514
515                 @Override
516                 public void onSwipeOutBegin() {
517                     mActionBar.hide();
518                     mFilmstripCoversPreview = false;
519                     updatePreviewVisibility();
520                 }
521
522                 @Override
523                 public void onFilmstripHidden() {
524                     mFilmstripVisible = false;
525                     CameraActivity.this.setFilmstripUiVisibility(false);
526                     // When the user hide the filmstrip (either swipe out or
527                     // tap on back key) we move to the first item so next time
528                     // when the user swipe in the filmstrip, the most recent
529                     // one is shown.
530                     mFilmstripController.goToFirstItem();
531                 }
532
533                 @Override
534                 public void onFilmstripShown() {
535                     mFilmstripVisible = true;
536                     decrementPeekAnimPlayTimes();
537                     updateUiByData(mFilmstripController.getCurrentId());
538                 }
539
540                 @Override
541                 public void onFocusedDataLongPressed(int dataId) {
542                     // Do nothing.
543                 }
544
545                 @Override
546                 public void onFocusedDataPromoted(int dataID) {
547                     UsageStatistics.photoInteraction(
548                             UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
549                             eventprotos.CameraEvent.InteractionType.DELETE,
550                             InteractionCause.SWIPE_UP);
551
552                     removeData(dataID);
553                 }
554
555                 @Override
556                 public void onFocusedDataDemoted(int dataID) {
557                     UsageStatistics.photoInteraction(
558                             UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
559                             eventprotos.CameraEvent.InteractionType.DELETE,
560                             InteractionCause.SWIPE_DOWN);
561
562                     removeData(dataID);
563                 }
564
565                 @Override
566                 public void onEnterFullScreenUiShown(int dataId) {
567                     if (mFilmstripVisible) {
568                         CameraActivity.this.setFilmstripUiVisibility(true);
569                     }
570                 }
571
572                 @Override
573                 public void onLeaveFullScreenUiShown(int dataId) {
574                     // Do nothing.
575                 }
576
577                 @Override
578                 public void onEnterFullScreenUiHidden(int dataId) {
579                     if (mFilmstripVisible) {
580                         CameraActivity.this.setFilmstripUiVisibility(false);
581                     }
582                 }
583
584                 @Override
585                 public void onLeaveFullScreenUiHidden(int dataId) {
586                     // Do nothing.
587                 }
588
589                 @Override
590                 public void onEnterFilmstrip(int dataId) {
591                     if (mFilmstripVisible) {
592                         CameraActivity.this.setFilmstripUiVisibility(true);
593                     }
594                 }
595
596                 @Override
597                 public void onLeaveFilmstrip(int dataId) {
598                     // Do nothing.
599                 }
600
601                 @Override
602                 public void onDataReloaded() {
603                     if (!mFilmstripVisible) {
604                         return;
605                     }
606                     updateUiByData(mFilmstripController.getCurrentId());
607                 }
608
609                 @Override
610                 public void onDataUpdated(int dataId) {
611                     if (!mFilmstripVisible) {
612                         return;
613                     }
614                     updateUiByData(mFilmstripController.getCurrentId());
615                 }
616
617                 @Override
618                 public void onEnterZoomView(int dataID) {
619                     if (mFilmstripVisible) {
620                         CameraActivity.this.setFilmstripUiVisibility(false);
621                     }
622                 }
623
624                 @Override
625                 public void onDataFocusChanged(final int prevDataId, final int newDataId) {
626                     if (!mFilmstripVisible) {
627                         return;
628                     }
629                     // TODO: This callback is UI event callback, should always
630                     // happen on UI thread. Find the reason for this
631                     // runOnUiThread() and fix it.
632                     runOnUiThread(new Runnable() {
633                         @Override
634                         public void run() {
635                             updateUiByData(newDataId);
636                         }
637                     });
638                 }
639
640                 @Override
641                 public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
642                     mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
643                 }
644             };
645
646     private final LocalDataAdapter.LocalDataListener mLocalDataListener =
647             new LocalDataAdapter.LocalDataListener() {
648                 @Override
649                 public void onMetadataUpdated(List<Integer> updatedData) {
650                     int currentDataId = mFilmstripController.getCurrentId();
651                     for (Integer dataId : updatedData) {
652                         if (dataId == currentDataId) {
653                             updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
654                         }
655                     }
656                 }
657             };
658
659     public void gotoGallery() {
660         UsageStatistics.changeScreen(NavigationChange.Mode.FILMSTRIP,
661                 InteractionCause.BUTTON);
662
663         mFilmstripController.goToNextItem();
664     }
665
666     /**
667      * If 'visible' is false, this hides the action bar and switches the
668      * filmstrip UI to lights-out mode.
669      *
670      * @param visible is false, this hides the action bar and switches the
671      *            filmstrip UI to lights-out mode.
672      */
673     private void setFilmstripUiVisibility(boolean visible) {
674         int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
675         int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE
676                 : View.SYSTEM_UI_FLAG_FULLSCREEN);
677         if (newSystemUIVisibility != currentSystemUIVisibility) {
678             mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
679         }
680
681         mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
682         if (visible != mActionBar.isShowing()) {
683             if (visible) {
684                 mActionBar.show();
685             } else {
686                 mActionBar.hide();
687             }
688         }
689         mFilmstripCoversPreview = visible;
690         updatePreviewVisibility();
691     }
692
693     private void hideSessionProgress() {
694         mCameraAppUI.getFilmstripBottomControls().hideProgress();
695     }
696
697     private void showSessionProgress(CharSequence message) {
698         CameraAppUI.BottomPanel controls =  mCameraAppUI.getFilmstripBottomControls();
699         controls.setProgressText(message);
700         controls.hideControls();
701         controls.hideProgressError();
702         controls.showProgress();
703     }
704
705     private void showProcessError(CharSequence message) {
706         mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
707     }
708
709     private void updateSessionProgress(int progress) {
710         mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
711     }
712
713     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
714     private void setupNfcBeamPush() {
715         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
716         if (adapter == null) {
717             return;
718         }
719
720         if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
721             // Disable beaming
722             adapter.setNdefPushMessage(null, CameraActivity.this);
723             return;
724         }
725
726         adapter.setBeamPushUris(null, CameraActivity.this);
727         adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
728             @Override
729             public Uri[] createBeamUris(NfcEvent event) {
730                 return mNfcPushUris;
731             }
732         }, CameraActivity.this);
733     }
734
735     @Override
736     public void onMenuVisibilityChanged(boolean isVisible) {
737         // TODO: Remove this or bring back the original implementation: cancel
738         // auto-hide actionbar.
739     }
740
741     @Override
742     public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
743         int currentDataId = mFilmstripController.getCurrentId();
744         if (currentDataId < 0) {
745             return false;
746         }
747         UsageStatistics.photoInteraction(
748                 UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
749                 eventprotos.CameraEvent.InteractionType.SHARE,
750                 InteractionCause.BUTTON);
751         // TODO add intent.getComponent().getPackageName()
752         return true;
753     }
754
755     // Note: All callbacks come back on the main thread.
756     private final SessionListener mSessionListener =
757             new SessionListener() {
758                 @Override
759                 public void onSessionQueued(final Uri uri) {
760                     if (!Storage.isSessionUri(uri)) {
761                         return;
762                     }
763                     LocalSessionData newData = new LocalSessionData(uri);
764                     mDataAdapter.addData(newData);
765                 }
766
767                 @Override
768                 public void onSessionDone(final Uri sessionUri) {
769                     Log.v(TAG, "onSessionDone:" + sessionUri);
770                     Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
771                     if (contentUri == null) {
772                         mDataAdapter.refresh(sessionUri);
773                         return;
774                     }
775                     LocalData newData = LocalMediaData.PhotoData.fromContentUri(
776                             getContentResolver(), contentUri);
777
778                     final int pos = mDataAdapter.findDataByContentUri(sessionUri);
779                     if (pos == -1) {
780                         // We do not have a placeholder for this image, perhaps due to the
781                         // activity crashing or being killed.
782                         mDataAdapter.addData(newData);
783                     }  else  {
784                         mDataAdapter.updateData(pos, newData);
785                     }
786                 }
787
788                 @Override
789                 public void onSessionProgress(final Uri uri, final int progress) {
790                     if (progress < 0) {
791                         // Do nothing, there is no task for this URI.
792                         return;
793                     }
794                     int currentDataId = mFilmstripController.getCurrentId();
795                     if (currentDataId == -1) {
796                         return;
797                     }
798                     if (uri.equals(
799                             mDataAdapter.getLocalData(currentDataId).getUri())) {
800                         updateSessionProgress(progress);
801                     }
802                 }
803
804                 @Override
805                 public void onSessionUpdated(Uri uri) {
806                     mDataAdapter.refresh(uri);
807                 }
808
809                 @Override
810                 public void onSessionPreviewAvailable(Uri uri) {
811                     mDataAdapter.refresh(uri);
812                     int dataId = mDataAdapter.findDataByContentUri(uri);
813                     if (dataId != -1) {
814                         startPeekAnimation(mDataAdapter.getLocalData(dataId));
815                     }
816                 }
817
818                 @Override
819                 public void onSessionFailed(Uri uri, CharSequence reason) {
820                     Log.v(TAG, "onSessionFailed:" + uri);
821
822                     int failedDataId = mDataAdapter.findDataByContentUri(uri);
823                     int currentDataId = mFilmstripController.getCurrentId();
824
825                     if (currentDataId == failedDataId) {
826                         updateSessionProgress(0);
827                         showProcessError(reason);
828                     }
829                     // HERE
830                     mDataAdapter.refresh(uri);
831                 }
832             };
833
834     @Override
835     public Context getAndroidContext() {
836         return mAppContext;
837     }
838
839     @Override
840     public void launchActivityByIntent(Intent intent) {
841         startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
842     }
843
844     @Override
845     public int getCurrentModuleIndex() {
846         return mCurrentModeIndex;
847     }
848
849     @Override
850     public ModuleController getCurrentModuleController() {
851         return mCurrentModule;
852     }
853
854     @Override
855     public int getQuickSwitchToModuleId(int currentModuleIndex) {
856         return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
857                 mAppContext);
858     }
859
860     @Override
861     public SurfaceTexture getPreviewBuffer() {
862         // TODO: implement this
863         return null;
864     }
865
866     @Override
867     public void onPreviewReadyToStart() {
868         mCameraAppUI.onPreviewReadyToStart();
869     }
870
871     @Override
872     public void onPreviewStarted() {
873         mCameraAppUI.onPreviewStarted();
874     }
875
876     @Override
877     public void addPreviewAreaSizeChangedListener(
878             PreviewStatusListener.PreviewAreaChangedListener listener) {
879         mCameraAppUI.addPreviewAreaChangedListener(listener);
880     }
881
882     @Override
883     public void removePreviewAreaSizeChangedListener(
884             PreviewStatusListener.PreviewAreaChangedListener listener) {
885         mCameraAppUI.removePreviewAreaChangedListener(listener);
886     }
887
888     @Override
889     public void setupOneShotPreviewListener() {
890         mCameraController.setOneShotPreviewCallback(mMainHandler,
891                 new CameraManager.CameraPreviewDataCallback() {
892                     @Override
893                     public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
894                         mCurrentModule.onPreviewInitialDataReceived();
895                         mCameraAppUI.onNewPreviewFrame();
896                     }
897                 });
898     }
899
900     @Override
901     public void updatePreviewAspectRatio(float aspectRatio) {
902         mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
903     }
904
905     @Override
906     public boolean shouldShowShimmy() {
907         int remainingTimes = mSettingsManager.getInt(
908                 SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX);
909         return remainingTimes > 0;
910     }
911
912     @Override
913     public void decrementShimmyPlayTimes() {
914         int remainingTimes = mSettingsManager.getInt(
915                 SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX) - 1;
916         if (remainingTimes >= 0) {
917             mSettingsManager.setInt(SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX,
918                     remainingTimes);
919         }
920     }
921
922     @Override
923     public void updatePreviewTransform(Matrix matrix) {
924         mCameraAppUI.updatePreviewTransform(matrix);
925     }
926
927     @Override
928     public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
929         mCameraAppUI.setPreviewStatusListener(previewStatusListener);
930     }
931
932     @Override
933     public FrameLayout getModuleLayoutRoot() {
934         return mCameraAppUI.getModuleRootView();
935     }
936
937     @Override
938     public void setShutterEventsListener(ShutterEventsListener listener) {
939         // TODO: implement this
940     }
941
942     @Override
943     public void setShutterEnabled(boolean enabled) {
944         // TODO: implement this
945     }
946
947     @Override
948     public boolean isShutterEnabled() {
949         // TODO: implement this
950         return false;
951     }
952
953     @Override
954     public void startPreCaptureAnimation() {
955         mCameraAppUI.startPreCaptureAnimation();
956     }
957
958     @Override
959     public void cancelPreCaptureAnimation() {
960         // TODO: implement this
961     }
962
963     @Override
964     public void startPostCaptureAnimation() {
965         // TODO: implement this
966     }
967
968     @Override
969     public void startPostCaptureAnimation(Bitmap thumbnail) {
970         // TODO: implement this
971     }
972
973     @Override
974     public void cancelPostCaptureAnimation() {
975         // TODO: implement this
976     }
977
978     @Override
979     public OrientationManager getOrientationManager() {
980         return mOrientationManager;
981     }
982
983     @Override
984     public LocationManager getLocationManager() {
985         return mLocationManager;
986     }
987
988     @Override
989     public void lockOrientation() {
990         if (mOrientationManager != null) {
991             mOrientationManager.lockOrientation();
992         }
993     }
994
995     @Override
996     public void unlockOrientation() {
997         if (mOrientationManager != null) {
998             mOrientationManager.unlockOrientation();
999         }
1000     }
1001
1002     /**
1003      * Decrement the remaining play times for peek animation.
1004      */
1005     private void decrementPeekAnimPlayTimes() {
1006         int remainingTimes = mSettingsManager.getInt(
1007                 SettingsManager.SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX) - 1;
1008         if (remainingTimes < 0) {
1009             return;
1010         }
1011         mSettingsManager
1012                 .setInt(SettingsManager.SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX,
1013                         remainingTimes);
1014     }
1015
1016     /**
1017      * Starts the filmstrip peek animation if the filmstrip is not visible.
1018      * Only {@link LocalData#LOCAL_IMAGE}, {@link
1019      * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
1020      * LocalData#LOCAL_VIDEO} are supported.
1021      *
1022      * @param data The data to peek.
1023      */
1024     private void startPeekAnimation(final LocalData data) {
1025         if (mFilmstripVisible || mPeekAnimationHandler == null) {
1026             return;
1027         }
1028
1029         int dataType = data.getLocalDataType();
1030         if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
1031                 dataType != LocalData.LOCAL_VIDEO) {
1032             return;
1033         }
1034
1035         int remainingTimes = mSettingsManager.getInt(
1036                 SettingsManager.SETTING_FILMSTRIP_PEEK_ANIM_REMAINING_PLAY_TIMES_INDEX);
1037         if (remainingTimes <= 0) {
1038             return;
1039         }
1040         mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
1041             @Override
1042             public void onCallback(Bitmap result) {
1043                 mCameraAppUI.startPeekAnimation(result, true);
1044             }
1045         });
1046     }
1047
1048     @Override
1049     public void notifyNewMedia(Uri uri) {
1050         ContentResolver cr = getContentResolver();
1051         String mimeType = cr.getType(uri);
1052         if (LocalDataUtil.isMimeTypeVideo(mimeType)) {
1053             sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1054             LocalData newData = LocalMediaData.VideoData.fromContentUri(getContentResolver(), uri);
1055             if (newData == null) {
1056                 Log.e(TAG, "Can't find video data in content resolver:" + uri);
1057                 return;
1058             }
1059             if (mDataAdapter.addData(newData)) {
1060                 startPeekAnimation(newData);
1061             }
1062         } else if (LocalDataUtil.isMimeTypeImage(mimeType)) {
1063             CameraUtil.broadcastNewPicture(mAppContext, uri);
1064             LocalData newData = LocalMediaData.PhotoData.fromContentUri(getContentResolver(), uri);
1065             if (newData == null) {
1066                 Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1067                 return;
1068             }
1069             if (mDataAdapter.addData(newData)) {
1070                 startPeekAnimation(newData);
1071             }
1072         } else {
1073             android.util.Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1074         }
1075     }
1076
1077     @Override
1078     public void enableKeepScreenOn(boolean enabled) {
1079         if (mPaused) {
1080             return;
1081         }
1082
1083         mKeepScreenOn = enabled;
1084         if (mKeepScreenOn) {
1085             mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1086             getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1087         } else {
1088             keepScreenOnForAWhile();
1089         }
1090     }
1091
1092     @Override
1093     public CameraProvider getCameraProvider() {
1094         return mCameraController;
1095     }
1096
1097     private void removeData(int dataID) {
1098         mDataAdapter.removeData(dataID);
1099         if (mDataAdapter.getTotalNumber() > 1) {
1100             showUndoDeletionBar();
1101         } else {
1102             // If camera preview is the only view left in filmstrip,
1103             // no need to show undo bar.
1104             mPendingDeletion = true;
1105             performDeletion();
1106             if (mFilmstripVisible) {
1107                 mCameraAppUI.getFilmstripContentPanel().animateHide();
1108             }
1109         }
1110     }
1111
1112     @Override
1113     public boolean onOptionsItemSelected(MenuItem item) {
1114         // Handle presses on the action bar items
1115         switch (item.getItemId()) {
1116             case android.R.id.home:
1117                 if (mFilmstripVisible && startGallery()) {
1118                     return true;
1119                 }
1120                 onBackPressed();
1121                 return true;
1122             case R.id.action_details:
1123                 showDetailsDialog(mFilmstripController.getCurrentId());
1124                 return true;
1125             default:
1126                 return super.onOptionsItemSelected(item);
1127         }
1128     }
1129
1130     private boolean isCaptureIntent() {
1131         if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1132                 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1133                 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1134             return true;
1135         } else {
1136             return false;
1137         }
1138     }
1139
1140     private final SettingsManager.StrictUpgradeCallback mStrictUpgradeCallback
1141         = new SettingsManager.StrictUpgradeCallback() {
1142                 @Override
1143                 public void upgrade(SettingsManager settingsManager, int version) {
1144                     // Show the location dialog on upgrade if
1145                     //  (a) the user has never set this option (status quo).
1146                     //  (b) the user opt'ed out previously.
1147                     if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION)) {
1148                         // Location is set in the source file defined for this setting.
1149                         // Remove the setting if the value is false to launch the dialog.
1150                         if (!settingsManager.getBoolean(SettingsManager.SETTING_RECORD_LOCATION)) {
1151                             settingsManager.remove(SettingsManager.SETTING_RECORD_LOCATION);
1152                         }
1153                     } else {
1154                         // Location is not set, check to see if we're upgrading from
1155                         // a different source file.
1156                         if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION,
1157                                                   SettingsManager.SOURCE_GLOBAL)) {
1158                             boolean location = settingsManager.getBoolean(
1159                                 SettingsManager.SETTING_RECORD_LOCATION,
1160                                 SettingsManager.SOURCE_GLOBAL);
1161                             if (location) {
1162                                 // Set the old setting only if the value is true, to prevent
1163                                 // launching the dialog.
1164                                 settingsManager.setBoolean(
1165                                     SettingsManager.SETTING_RECORD_LOCATION, location);
1166                             }
1167                         }
1168                     }
1169
1170                     settingsManager.remove(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1171                 }
1172             };
1173
1174     private final CameraManager.CameraExceptionCallback mCameraDefaultExceptionCallback
1175         = new CameraManager.CameraExceptionCallback() {
1176                 @Override
1177                 public void onCameraException(RuntimeException e) {
1178                     Log.d(TAG, "Camera Exception", e);
1179                     CameraUtil.showErrorAndFinish(CameraActivity.this,
1180                             R.string.cannot_connect_camera);
1181                 }
1182             };
1183
1184     @Override
1185     public void onCreate(Bundle state) {
1186         super.onCreate(state);
1187         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1188         mOnCreateTime = System.currentTimeMillis();
1189         mAppContext = getApplicationContext();
1190         GcamHelper.init(getContentResolver());
1191
1192         getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1193         setContentView(R.layout.activity_main);
1194         mActionBar = getActionBar();
1195         mActionBar.addOnMenuVisibilityListener(this);
1196         mMainHandler = new MainHandler(this, getMainLooper());
1197         mCameraController =
1198                 new CameraController(mAppContext, this, mMainHandler,
1199                         CameraManagerFactory.getAndroidCameraManager());
1200         mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback,
1201                 mMainHandler);
1202
1203         mPreferences = new ComboPreferences(mAppContext);
1204
1205         mSettingsManager = new SettingsManager(mAppContext, this,
1206                 mCameraController.getNumberOfCameras(), mStrictUpgradeCallback);
1207
1208         // Remove this after we get rid of ComboPreferences.
1209         int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1210         mPreferences.setLocalId(mAppContext, cameraId);
1211         CameraSettings.upgradeGlobalPreferences(mPreferences,
1212                 mCameraController.getNumberOfCameras());
1213         // TODO: Try to move all the resources allocation to happen as soon as
1214         // possible so we can call module.init() at the earliest time.
1215         mModuleManager = new ModuleManagerImpl();
1216         ModulesInfo.setupModules(mAppContext, mModuleManager);
1217
1218         mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1219         mModeListView.init(mModuleManager.getSupportedModeIndexList());
1220         if (ApiHelper.HAS_ROTATION_ANIMATION) {
1221             setRotationAnimation();
1222         }
1223         mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1224             @Override
1225             public void onVisibilityChanged(boolean visible) {
1226                 mModeListVisible = visible;
1227                 updatePreviewVisibility();
1228             }
1229         });
1230
1231         // Check if this is in the secure camera mode.
1232         Intent intent = getIntent();
1233         String action = intent.getAction();
1234         if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1235                 || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1236             mSecureCamera = true;
1237         } else {
1238             mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1239         }
1240
1241         if (mSecureCamera) {
1242             // Foreground event caused by lock screen startup.
1243             // It is necessary to log this in onCreate, to avoid the
1244             // onResume->onPause->onResume sequence.
1245             UsageStatistics.foregrounded(
1246                     eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1247
1248             // Change the window flags so that secure camera can show when
1249             // locked
1250             Window win = getWindow();
1251             WindowManager.LayoutParams params = win.getAttributes();
1252             params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1253             win.setAttributes(params);
1254
1255             // Filter for screen off so that we can finish secure camera
1256             // activity
1257             // when screen is off.
1258             IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1259             registerReceiver(mScreenOffReceiver, filter);
1260         }
1261         mCameraAppUI = new CameraAppUI(this,
1262                 (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1263
1264         mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1265
1266         mAboveFilmstripControlLayout =
1267                 (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1268
1269         // Add the session listener so we can track the session progress
1270         // updates.
1271         getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1272         mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1273         mFilmstripController.setImageGap(
1274                 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1275         mPanoramaViewHelper = new PanoramaViewHelper(this);
1276         mPanoramaViewHelper.onCreate();
1277         // Set up the camera preview first so the preview shows up ASAP.
1278         mDataAdapter = new CameraDataAdapter(mAppContext,
1279                 new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
1280         mDataAdapter.setLocalDataListener(mLocalDataListener);
1281
1282         mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1283                 mDataAdapter);
1284
1285         mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1286
1287         mLocationManager = new LocationManager(mAppContext);
1288
1289         int modeIndex = -1;
1290         int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1291         int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1292         int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1293         if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1294                 || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1295             modeIndex = videoIndex;
1296         } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1297                 || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1298             // TODO: synchronize mode options with photo module without losing
1299             // HDR+ preferences.
1300             modeIndex = photoIndex;
1301         } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1302                         .getAction())
1303                 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1304             modeIndex = mSettingsManager.getInt(
1305                 SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX);
1306         } else {
1307             // If the activity has not been started using an explicit intent,
1308             // read the module index from the last time the user changed modes
1309             modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1310             if ((modeIndex == gcamIndex &&
1311                     !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1312                 modeIndex = photoIndex;
1313             }
1314         }
1315
1316         mOrientationManager = new OrientationManagerImpl(this);
1317         mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1318
1319         setModuleFromModeIndex(modeIndex);
1320         mCameraAppUI.prepareModuleUI();
1321         mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1322
1323         if (!mSecureCamera) {
1324             mFilmstripController.setDataAdapter(mDataAdapter);
1325             if (!isCaptureIntent()) {
1326                 mDataAdapter.requestLoad();
1327             }
1328         } else {
1329             // Put a lock placeholder as the last image by setting its date to
1330             // 0.
1331             ImageView v = (ImageView) getLayoutInflater().inflate(
1332                     R.layout.secure_album_placeholder, null);
1333             v.setOnClickListener(new View.OnClickListener() {
1334                 @Override
1335                 public void onClick(View view) {
1336                     UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1337                             InteractionCause.BUTTON);
1338                     startGallery();
1339                     finish();
1340                 }
1341             });
1342             mDataAdapter = new FixedLastDataAdapter(
1343                     mAppContext,
1344                     mDataAdapter,
1345                     new SimpleViewData(
1346                             v,
1347                             v.getDrawable().getIntrinsicWidth(),
1348                             v.getDrawable().getIntrinsicHeight(),
1349                             0, 0));
1350             // Flush out all the original data.
1351             mDataAdapter.flush();
1352             mFilmstripController.setDataAdapter(mDataAdapter);
1353         }
1354
1355         setupNfcBeamPush();
1356
1357         mLocalImagesObserver = new LocalMediaObserver();
1358         mLocalVideosObserver = new LocalMediaObserver();
1359
1360         getContentResolver().registerContentObserver(
1361                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1362                 mLocalImagesObserver);
1363         getContentResolver().registerContentObserver(
1364                 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1365                 mLocalVideosObserver);
1366         if (FeedbackHelper.feedbackAvailable()) {
1367             mFeedbackHelper = new FeedbackHelper(mAppContext);
1368         }
1369     }
1370
1371     /**
1372      * Call this whenever the mode drawer or filmstrip change the visibility
1373      * state.
1374      */
1375     private void updatePreviewVisibility() {
1376         if (mCurrentModule == null) {
1377             return;
1378         }
1379
1380         int visibility = getPreviewVisibility();
1381         updatePreviewRendering(visibility);
1382         mCurrentModule.onPreviewVisibilityChanged(visibility);
1383     }
1384
1385     private void updatePreviewRendering(int visibility) {
1386         if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1387             mCameraAppUI.pausePreviewRendering();
1388         } else {
1389             mCameraAppUI.resumePreviewRendering();
1390         }
1391     }
1392
1393     private int getPreviewVisibility() {
1394         if (mFilmstripCoversPreview) {
1395             return ModuleController.VISIBILITY_HIDDEN;
1396         } else if (mModeListVisible){
1397             return ModuleController.VISIBILITY_COVERED;
1398         } else {
1399             return ModuleController.VISIBILITY_VISIBLE;
1400         }
1401     }
1402
1403     private void setRotationAnimation() {
1404         int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1405         rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1406         Window win = getWindow();
1407         WindowManager.LayoutParams winParams = win.getAttributes();
1408         winParams.rotationAnimation = rotationAnimation;
1409         win.setAttributes(winParams);
1410     }
1411
1412     @Override
1413     public void onUserInteraction() {
1414         super.onUserInteraction();
1415         if (!isFinishing()) {
1416             keepScreenOnForAWhile();
1417         }
1418     }
1419
1420     @Override
1421     public boolean dispatchTouchEvent(MotionEvent ev) {
1422         boolean result = super.dispatchTouchEvent(ev);
1423         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1424             // Real deletion is postponed until the next user interaction after
1425             // the gesture that triggers deletion. Until real deletion is
1426             // performed, users can click the undo button to bring back the
1427             // image that they chose to delete.
1428             if (mPendingDeletion && !mIsUndoingDeletion) {
1429                 performDeletion();
1430             }
1431         }
1432         return result;
1433     }
1434
1435     @Override
1436     public void onPause() {
1437         mPaused = true;
1438         mPeekAnimationHandler = null;
1439         mPeekAnimationThread.quitSafely();
1440         mPeekAnimationThread = null;
1441         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1442
1443         // Delete photos that are pending deletion
1444         performDeletion();
1445         mCurrentModule.pause();
1446         mOrientationManager.pause();
1447         // Close the camera and wait for the operation done.
1448         mCameraController.closeCamera();
1449         mPanoramaViewHelper.onPause();
1450
1451         mLocalImagesObserver.setForegroundChangeListener(null);
1452         mLocalImagesObserver.setActivityPaused(true);
1453         mLocalVideosObserver.setActivityPaused(true);
1454         mPreloader.cancelAllLoads();
1455         resetScreenOn();
1456         super.onPause();
1457     }
1458
1459     @Override
1460     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1461         if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1462             mResetToPreviewOnResume = false;
1463         } else {
1464             super.onActivityResult(requestCode, resultCode, data);
1465         }
1466     }
1467
1468     @Override
1469     public void onResume() {
1470         mPaused = false;
1471         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1472
1473         mLastLayoutOrientation = getResources().getConfiguration().orientation;
1474
1475         // TODO: Handle this in OrientationManager.
1476         // Auto-rotate off
1477         if (Settings.System.getInt(getContentResolver(),
1478                 Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1479             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1480             mAutoRotateScreen = false;
1481         } else {
1482             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1483             mAutoRotateScreen = true;
1484         }
1485
1486         if (isCaptureIntent()) {
1487             // Foreground event caused by photo or video capure intent.
1488             UsageStatistics.foregrounded(
1489                     eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1490         } else if (!mSecureCamera) {
1491             // Foreground event that is not caused by an intent.
1492             UsageStatistics.foregrounded(
1493                     eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1494         }
1495
1496         Drawable galleryLogo;
1497         if (mSecureCamera) {
1498             mGalleryIntent = null;
1499             galleryLogo = null;
1500         } else {
1501             mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1502             galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1503         }
1504         if (galleryLogo == null) {
1505             try {
1506                 galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1507             } catch (PackageManager.NameNotFoundException e) {
1508                 Log.e(TAG, "Can't get the activity logo");
1509             }
1510         }
1511         if (mGalleryIntent != null) {
1512             mActionBar.setDisplayUseLogoEnabled(true);
1513         }
1514         mActionBar.setLogo(galleryLogo);
1515         mOrientationManager.resume();
1516         super.onResume();
1517         mPeekAnimationThread = new HandlerThread("Peek animation");
1518         mPeekAnimationThread.start();
1519         mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1520         mCurrentModule.resume();
1521         setSwipingEnabled(true);
1522
1523         if (mResetToPreviewOnResume) {
1524             mCameraAppUI.resume();
1525         } else {
1526             LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1527             if (data != null) {
1528                 mDataAdapter.refresh(data.getUri());
1529             }
1530         }
1531         // The share button might be disabled to avoid double tapping.
1532         mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1533         // Default is showing the preview, unless disabled by explicitly
1534         // starting an activity we want to return from to the filmstrip rather
1535         // than the preview.
1536         mResetToPreviewOnResume = true;
1537
1538         if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1539                 || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1540             if (!mSecureCamera) {
1541                 // If it's secure camera, requestLoad() should not be called
1542                 // as it will load all the data.
1543                 if (!mFilmstripVisible) {
1544                     mDataAdapter.requestLoad();
1545                 } else {
1546                     mDataAdapter.requestLoadNewPhotos();
1547                 }
1548             }
1549         }
1550         mLocalImagesObserver.setActivityPaused(false);
1551         mLocalVideosObserver.setActivityPaused(false);
1552         if (!mSecureCamera) {
1553             mLocalImagesObserver.setForegroundChangeListener(
1554                     new LocalMediaObserver.ChangeListener() {
1555                 @Override
1556                 public void onChange() {
1557                     mDataAdapter.requestLoadNewPhotos();
1558                 }
1559             });
1560         }
1561
1562         keepScreenOnForAWhile();
1563
1564         mPanoramaViewHelper.onResume();
1565         ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1566         syncLocationManagerSetting();
1567
1568         final int previewVisibility = getPreviewVisibility();
1569         updatePreviewRendering(previewVisibility);
1570     }
1571
1572     @Override
1573     public void onStart() {
1574         super.onStart();
1575         mPanoramaViewHelper.onStart();
1576         if (mResetToPreviewOnResume) {
1577             mCameraAppUI.resume();
1578             mResetToPreviewOnResume = false;
1579         }
1580     }
1581
1582     @Override
1583     protected void onStop() {
1584         mPanoramaViewHelper.onStop();
1585         if (mFeedbackHelper != null) {
1586             mFeedbackHelper.stopFeedback();
1587         }
1588
1589         mLocationManager.disconnect();
1590         super.onStop();
1591     }
1592
1593     @Override
1594     public void onDestroy() {
1595         if (mSecureCamera) {
1596             unregisterReceiver(mScreenOffReceiver);
1597         }
1598         mActionBar.removeOnMenuVisibilityListener(this);
1599         mSettingsManager.removeAllListeners();
1600         mCameraController.removeCallbackReceiver();
1601         getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1602         getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1603         getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1604         mCameraAppUI.onDestroy();
1605         mCameraController = null;
1606         mSettingsManager = null;
1607         mCameraAppUI = null;
1608         mOrientationManager = null;
1609         mButtonManager = null;
1610         CameraManagerFactory.recycle();
1611         super.onDestroy();
1612     }
1613
1614     @Override
1615     public void onConfigurationChanged(Configuration config) {
1616         super.onConfigurationChanged(config);
1617         Log.v(TAG, "onConfigurationChanged");
1618         if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1619             return;
1620         }
1621
1622         if (mLastLayoutOrientation != config.orientation) {
1623             mLastLayoutOrientation = config.orientation;
1624             mCurrentModule.onLayoutOrientationChanged(
1625                     mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1626         }
1627     }
1628
1629     @Override
1630     public boolean onKeyDown(int keyCode, KeyEvent event) {
1631         if (!mFilmstripVisible) {
1632             if (mCurrentModule.onKeyDown(keyCode, event)) {
1633                 return true;
1634             }
1635             // Prevent software keyboard or voice search from showing up.
1636             if (keyCode == KeyEvent.KEYCODE_SEARCH
1637                     || keyCode == KeyEvent.KEYCODE_MENU) {
1638                 if (event.isLongPress()) {
1639                     return true;
1640                 }
1641             }
1642         }
1643
1644         return super.onKeyDown(keyCode, event);
1645     }
1646
1647     @Override
1648     public boolean onKeyUp(int keyCode, KeyEvent event) {
1649         if (!mFilmstripVisible) {
1650             // If a module is in the middle of capture, it should
1651             // consume the key event.
1652             if (mCurrentModule.onKeyUp(keyCode, event)) {
1653                 return true;
1654             } else if (keyCode == KeyEvent.KEYCODE_MENU
1655                     || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1656                 // Let the mode list view consume the event.
1657                 mModeListView.onMenuPressed();
1658                 return true;
1659             } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1660                 mCameraAppUI.showFilmstrip();
1661                 return true;
1662             }
1663         } else {
1664             if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1665                 mFilmstripController.goToNextItem();
1666                 return true;
1667             } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1668                 boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1669                 if (!wentToPrevious) {
1670                   // at beginning of filmstrip, hide and go back to preview
1671                   mCameraAppUI.hideFilmstrip();
1672                 }
1673                 return true;
1674             }
1675         }
1676         return super.onKeyUp(keyCode, event);
1677     }
1678
1679     @Override
1680     public void onBackPressed() {
1681         if (!mCameraAppUI.onBackPressed()) {
1682             if (!mCurrentModule.onBackPressed()) {
1683                 super.onBackPressed();
1684             }
1685         }
1686     }
1687
1688     @Override
1689     public boolean isAutoRotateScreen() {
1690         // TODO: Move to OrientationManager.
1691         return mAutoRotateScreen;
1692     }
1693
1694     @Override
1695     public boolean onCreateOptionsMenu(Menu menu) {
1696         MenuInflater inflater = getMenuInflater();
1697         inflater.inflate(R.menu.filmstrip_menu, menu);
1698         mActionBarMenu = menu;
1699         return super.onCreateOptionsMenu(menu);
1700     }
1701
1702     protected void updateStorageSpace() {
1703         mStorageSpaceBytes = Storage.getAvailableSpace();
1704     }
1705
1706     protected long getStorageSpaceBytes() {
1707         return mStorageSpaceBytes;
1708     }
1709
1710     protected void updateStorageSpaceAndHint() {
1711         updateStorageSpace();
1712         updateStorageHint(mStorageSpaceBytes);
1713     }
1714
1715     protected void updateStorageHint(long storageSpace) {
1716         String message = null;
1717         if (storageSpace == Storage.UNAVAILABLE) {
1718             message = getString(R.string.no_storage);
1719         } else if (storageSpace == Storage.PREPARING) {
1720             message = getString(R.string.preparing_sd);
1721         } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1722             message = getString(R.string.access_sd_fail);
1723         } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1724             message = getString(R.string.spaceIsLow_content);
1725         }
1726
1727         if (message != null) {
1728             if (mStorageHint == null) {
1729                 mStorageHint = OnScreenHint.makeText(mAppContext, message);
1730             } else {
1731                 mStorageHint.setText(message);
1732             }
1733             mStorageHint.show();
1734         } else if (mStorageHint != null) {
1735             mStorageHint.cancel();
1736             mStorageHint = null;
1737         }
1738     }
1739
1740     protected void setResultEx(int resultCode) {
1741         mResultCodeForTesting = resultCode;
1742         setResult(resultCode);
1743     }
1744
1745     protected void setResultEx(int resultCode, Intent data) {
1746         mResultCodeForTesting = resultCode;
1747         mResultDataForTesting = data;
1748         setResult(resultCode, data);
1749     }
1750
1751     public int getResultCode() {
1752         return mResultCodeForTesting;
1753     }
1754
1755     public Intent getResultData() {
1756         return mResultDataForTesting;
1757     }
1758
1759     public boolean isSecureCamera() {
1760         return mSecureCamera;
1761     }
1762
1763     @Override
1764     public boolean isPaused() {
1765         return mPaused;
1766     }
1767
1768     @Override
1769     public int getPreferredChildModeIndex(int modeIndex) {
1770         if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1771             boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1772             if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1773                 modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1774             }
1775         }
1776         return modeIndex;
1777     }
1778
1779     @Override
1780     public void onModeSelected(int modeIndex) {
1781         if (mCurrentModeIndex == modeIndex) {
1782             return;
1783         }
1784
1785         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1786         // Record last used camera mode for quick switching
1787         if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1788                 || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1789             mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1790                     modeIndex);
1791         }
1792
1793         closeModule(mCurrentModule);
1794         int oldModuleIndex = mCurrentModeIndex;
1795
1796         // Select the correct module index from the mode switcher index.
1797         modeIndex = getPreferredChildModeIndex(modeIndex);
1798         setModuleFromModeIndex(modeIndex);
1799
1800         mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1801         mCameraAppUI.addShutterListener(mCurrentModule);
1802         openModule(mCurrentModule);
1803         mCurrentModule.onOrientationChanged(mLastRawOrientation);
1804         // Store the module index so we can use it the next time the Camera
1805         // starts up.
1806         SharedPreferences prefs = PreferenceManager
1807                 .getDefaultSharedPreferences(mAppContext);
1808         prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1809     }
1810
1811     /**
1812      * Shows the settings dialog.
1813      */
1814     @Override
1815     public void onSettingsSelected() {
1816         Intent intent = new Intent(this, CameraSettingsActivity.class);
1817         startActivity(intent);
1818     }
1819
1820     /**
1821      * Sets the mCurrentModuleIndex, creates a new module instance for the given
1822      * index an sets it as mCurrentModule.
1823      */
1824     private void setModuleFromModeIndex(int modeIndex) {
1825         ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1826         if (agent == null) {
1827             return;
1828         }
1829         if (!agent.requestAppForCamera()) {
1830             mCameraController.closeCamera();
1831         }
1832         mCurrentModeIndex = agent.getModuleId();
1833         mCurrentModule = (CameraModule) agent.createModule(this);
1834     }
1835
1836     @Override
1837     public SettingsManager getSettingsManager() {
1838         return mSettingsManager;
1839     }
1840
1841     @Override
1842     public CameraServices getServices() {
1843         return (CameraServices) getApplication();
1844     }
1845
1846     public List<String> getSupportedModeNames() {
1847         List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1848         List<String> supported = new ArrayList<String>();
1849
1850         for (Integer modeIndex : indices) {
1851             String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1852             if (name != null && !name.equals("")) {
1853                 supported.add(name);
1854             }
1855         }
1856         return supported;
1857     }
1858
1859     @Override
1860     public ButtonManager getButtonManager() {
1861         if (mButtonManager == null) {
1862             mButtonManager = new ButtonManager(this);
1863         }
1864         return mButtonManager;
1865     }
1866
1867     /**
1868      * Creates an AlertDialog appropriate for choosing whether to enable
1869      * location on the first run of the app.
1870      */
1871     public AlertDialog getFirstTimeLocationAlert() {
1872         AlertDialog.Builder builder = new AlertDialog.Builder(this);
1873         builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1874             @Override
1875             public void onCallback(Boolean locationOn) {
1876                 mSettingsManager.setLocation(locationOn, mLocationManager);
1877             }
1878         });
1879         if (builder != null) {
1880             return builder.create();
1881         } else {
1882             return null;
1883         }
1884     }
1885
1886     /**
1887      * Launches an ACTION_EDIT intent for the given local data item. If
1888      * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1889      * the user start either the tiny planet editor or another photo edior.
1890      *
1891      * @param data The data item to edit.
1892      */
1893     public void launchEditor(LocalData data) {
1894         Intent intent = new Intent(Intent.ACTION_EDIT)
1895                 .setDataAndType(data.getUri(), data.getMimeType())
1896                 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1897         try {
1898             launchActivityByIntent(intent);
1899         } catch (ActivityNotFoundException e) {
1900             launchActivityByIntent(Intent.createChooser(intent, null));
1901         }
1902     }
1903
1904     @Override
1905     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1906         super.onCreateContextMenu(menu, v, menuInfo);
1907
1908         MenuInflater inflater = getMenuInflater();
1909         inflater.inflate(R.menu.filmstrip_context_menu, menu);
1910     }
1911
1912     @Override
1913     public boolean onContextItemSelected(MenuItem item) {
1914         switch (item.getItemId()) {
1915             case R.id.tiny_planet_editor:
1916                 mMyFilmstripBottomControlListener.onTinyPlanet();
1917                 return true;
1918             case R.id.photo_editor:
1919                 mMyFilmstripBottomControlListener.onEdit();
1920                 return true;
1921         }
1922         return false;
1923     }
1924
1925     /**
1926      * Launch the tiny planet editor.
1927      *
1928      * @param data The data must be a 360 degree stereographically mapped
1929      *            panoramic image. It will not be modified, instead a new item
1930      *            with the result will be added to the filmstrip.
1931      */
1932     public void launchTinyPlanetEditor(LocalData data) {
1933         TinyPlanetFragment fragment = new TinyPlanetFragment();
1934         Bundle bundle = new Bundle();
1935         bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
1936         bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1937         fragment.setArguments(bundle);
1938         fragment.show(getFragmentManager(), "tiny_planet");
1939     }
1940
1941     private void openModule(CameraModule module) {
1942         module.init(this, isSecureCamera(), isCaptureIntent());
1943         module.resume();
1944         updatePreviewVisibility();
1945     }
1946
1947     private void closeModule(CameraModule module) {
1948         module.pause();
1949         mCameraAppUI.clearModuleUI();
1950     }
1951
1952     private void performDeletion() {
1953         if (!mPendingDeletion) {
1954             return;
1955         }
1956         hideUndoDeletionBar(false);
1957         mDataAdapter.executeDeletion();
1958     }
1959
1960     public void showUndoDeletionBar() {
1961         if (mPendingDeletion) {
1962             performDeletion();
1963         }
1964         Log.v(TAG, "showing undo bar");
1965         mPendingDeletion = true;
1966         if (mUndoDeletionBar == null) {
1967             ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1968                     mAboveFilmstripControlLayout, true);
1969             mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1970             View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1971             button.setOnClickListener(new View.OnClickListener() {
1972                 @Override
1973                 public void onClick(View view) {
1974                     mDataAdapter.undoDataRemoval();
1975                     hideUndoDeletionBar(true);
1976                 }
1977             });
1978             // Setting undo bar clickable to avoid touch events going through
1979             // the bar to the buttons (eg. edit button, etc) underneath the bar.
1980             mUndoDeletionBar.setClickable(true);
1981             // When there is user interaction going on with the undo button, we
1982             // do not want to hide the undo bar.
1983             button.setOnTouchListener(new View.OnTouchListener() {
1984                 @Override
1985                 public boolean onTouch(View v, MotionEvent event) {
1986                     if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1987                         mIsUndoingDeletion = true;
1988                     } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1989                         mIsUndoingDeletion = false;
1990                     }
1991                     return false;
1992                 }
1993             });
1994         }
1995         mUndoDeletionBar.setAlpha(0f);
1996         mUndoDeletionBar.setVisibility(View.VISIBLE);
1997         mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1998     }
1999
2000     private void hideUndoDeletionBar(boolean withAnimation) {
2001         Log.v(TAG, "Hiding undo deletion bar");
2002         mPendingDeletion = false;
2003         if (mUndoDeletionBar != null) {
2004             if (withAnimation) {
2005                 mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2006                         .setListener(new Animator.AnimatorListener() {
2007                             @Override
2008                             public void onAnimationStart(Animator animation) {
2009                                 // Do nothing.
2010                             }
2011
2012                             @Override
2013                             public void onAnimationEnd(Animator animation) {
2014                                 mUndoDeletionBar.setVisibility(View.GONE);
2015                             }
2016
2017                             @Override
2018                             public void onAnimationCancel(Animator animation) {
2019                                 // Do nothing.
2020                             }
2021
2022                             @Override
2023                             public void onAnimationRepeat(Animator animation) {
2024                                 // Do nothing.
2025                             }
2026                         }).start();
2027             } else {
2028                 mUndoDeletionBar.setVisibility(View.GONE);
2029             }
2030         }
2031     }
2032
2033     @Override
2034     public void onOrientationChanged(int orientation) {
2035         // We keep the last known orientation. So if the user first orient
2036         // the camera then point the camera to floor or sky, we still have
2037         // the correct orientation.
2038         if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2039             return;
2040         }
2041         mLastRawOrientation = orientation;
2042         if (mCurrentModule != null) {
2043             mCurrentModule.onOrientationChanged(orientation);
2044         }
2045     }
2046
2047     /**
2048      * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2049      * capture intent.
2050      *
2051      * @param enable {@code true} to enable swipe.
2052      */
2053     public void setSwipingEnabled(boolean enable) {
2054         // TODO: Bring back the functionality.
2055         if (isCaptureIntent()) {
2056             // lockPreview(true);
2057         } else {
2058             // lockPreview(!enable);
2059         }
2060     }
2061
2062     // Accessor methods for getting latency times used in performance testing
2063     public long getFirstPreviewTime() {
2064         if (mCurrentModule instanceof PhotoModule) {
2065             long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2066             if (coverHiddenTime != -1) {
2067                 return coverHiddenTime - mOnCreateTime;
2068             }
2069         }
2070         return -1;
2071     }
2072
2073     public long getAutoFocusTime() {
2074         return (mCurrentModule instanceof PhotoModule) ?
2075                 ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2076     }
2077
2078     public long getShutterLag() {
2079         return (mCurrentModule instanceof PhotoModule) ?
2080                 ((PhotoModule) mCurrentModule).mShutterLag : -1;
2081     }
2082
2083     public long getShutterToPictureDisplayedTime() {
2084         return (mCurrentModule instanceof PhotoModule) ?
2085                 ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2086     }
2087
2088     public long getPictureDisplayedToJpegCallbackTime() {
2089         return (mCurrentModule instanceof PhotoModule) ?
2090                 ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2091     }
2092
2093     public long getJpegCallbackFinishTime() {
2094         return (mCurrentModule instanceof PhotoModule) ?
2095                 ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2096     }
2097
2098     public long getCaptureStartTime() {
2099         return (mCurrentModule instanceof PhotoModule) ?
2100                 ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2101     }
2102
2103     public boolean isRecording() {
2104         return (mCurrentModule instanceof VideoModule) ?
2105                 ((VideoModule) mCurrentModule).isRecording() : false;
2106     }
2107
2108     public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
2109         return mCameraController;
2110     }
2111
2112     // For debugging purposes only.
2113     public CameraModule getCurrentModule() {
2114         return mCurrentModule;
2115     }
2116
2117     @Override
2118     public void showTutorial(AbstractTutorialOverlay tutorial) {
2119         mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2120     }
2121
2122     /**
2123      * Reads the current location recording settings and passes it on to the
2124      * location manager.
2125      */
2126     public void syncLocationManagerSetting() {
2127         mSettingsManager.syncLocationManager(mLocationManager);
2128     }
2129
2130     private void keepScreenOnForAWhile() {
2131         if (mKeepScreenOn) {
2132             return;
2133         }
2134         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2135         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2136         mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2137     }
2138
2139     private void resetScreenOn() {
2140         mKeepScreenOn = false;
2141         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2142         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2143     }
2144
2145     /**
2146      * @return {@code true} if the Gallery is launched successfully.
2147      */
2148     private boolean startGallery() {
2149         if (mGalleryIntent == null) {
2150             return false;
2151         }
2152         try {
2153             UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
2154             Intent startGalleryIntent = new Intent(mGalleryIntent);
2155             int currentDataId = mFilmstripController.getCurrentId();
2156             LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2157             if (currentLocalData != null) {
2158                 GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2159             }
2160             launchActivityByIntent(startGalleryIntent);
2161         } catch (ActivityNotFoundException e) {
2162             Log.w(TAG, "Failed to launch gallery activity, closing");
2163         }
2164         return false;
2165     }
2166
2167     private void setNfcBeamPushUriFromData(LocalData data) {
2168         final Uri uri = data.getUri();
2169         if (uri != Uri.EMPTY) {
2170             mNfcPushUris[0] = uri;
2171         } else {
2172             mNfcPushUris[0] = null;
2173         }
2174     }
2175
2176     /**
2177      * Updates the visibility of the filmstrip bottom controls and action bar.
2178      */
2179     private void updateUiByData(final int dataId) {
2180         final LocalData currentData = mDataAdapter.getLocalData(dataId);
2181         if (currentData == null) {
2182             Log.w(TAG, "Current data ID not found.");
2183             hideSessionProgress();
2184             return;
2185         }
2186         updateActionBarMenu(currentData);
2187
2188         /* Bottom controls. */
2189         updateBottomControlsByData(currentData);
2190
2191         if (isSecureCamera()) {
2192             // We cannot show buttons in secure camera since go to other
2193             // activities might create a security hole.
2194             mCameraAppUI.getFilmstripBottomControls().hideControls();
2195             return;
2196         }
2197
2198
2199         setNfcBeamPushUriFromData(currentData);
2200
2201         if (!mDataAdapter.isMetadataUpdated(dataId)) {
2202             mDataAdapter.updateMetadata(dataId);
2203         }
2204     }
2205
2206     /**
2207      * Updates the bottom controls based on the data.
2208      */
2209     private void updateBottomControlsByData(final LocalData currentData) {
2210
2211         final CameraAppUI.BottomPanel filmstripBottomPanel =
2212                 mCameraAppUI.getFilmstripBottomControls();
2213         filmstripBottomPanel.showControls();
2214         filmstripBottomPanel.setEditButtonVisibility(
2215                 currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2216         filmstripBottomPanel.setShareButtonVisibility(
2217                 currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2218         filmstripBottomPanel.setDeleteButtonVisibility(
2219                 currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2220
2221         /* Progress bar */
2222
2223         Uri contentUri = currentData.getUri();
2224         CaptureSessionManager sessionManager = getServices()
2225                 .getCaptureSessionManager();
2226
2227         if (sessionManager.hasErrorMessage(contentUri)) {
2228             showProcessError(sessionManager.getErrorMesage(contentUri));
2229         } else {
2230             filmstripBottomPanel.hideProgressError();
2231             CaptureSession session = sessionManager.getSession(contentUri);
2232
2233             if (session != null) {
2234                 int sessionProgress = session.getProgress();
2235
2236                 if (sessionProgress < 0) {
2237                     hideSessionProgress();
2238                 } else {
2239                     CharSequence progressMessage = session.getProgressMessage();
2240                     showSessionProgress(progressMessage);
2241                     updateSessionProgress(sessionProgress);
2242                 }
2243             } else {
2244                 hideSessionProgress();
2245             }
2246         }
2247
2248         /* View button */
2249
2250         // We need to add this to a separate DB.
2251         final int viewButtonVisibility;
2252         if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2253             viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2254         } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2255             viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2256         } else {
2257             viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2258         }
2259
2260         filmstripBottomPanel.setTinyPlanetEnabled(
2261                 PanoramaMetadataLoader.isPanorama360(currentData));
2262         filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2263     }
2264
2265     private class PeekAnimationHandler extends Handler {
2266         private class DataAndCallback {
2267             LocalData mData;
2268             com.android.camera.util.Callback<Bitmap> mCallback;
2269
2270             public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2271                     callback) {
2272                 mData = data;
2273                 mCallback = callback;
2274             }
2275         }
2276
2277         public PeekAnimationHandler(Looper looper) {
2278             super(looper);
2279         }
2280
2281         /**
2282          * Starts the animation decoding job and posts a {@code Runnable} back
2283          * when when the decoding is done.
2284          *
2285          * @param data The data item to decode the thumbnail for.
2286          * @param callback {@link com.android.camera.util.Callback} after the
2287          *                 decoding is done.
2288          */
2289         public void startDecodingJob(final LocalData data,
2290                 final com.android.camera.util.Callback<Bitmap> callback) {
2291             PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2292                     new DataAndCallback(data, callback)).sendToTarget();
2293         }
2294
2295         @Override
2296         public void handleMessage(Message msg) {
2297             final LocalData data = ((DataAndCallback) msg.obj).mData;
2298             final com.android.camera.util.Callback<Bitmap> callback =
2299                     ((DataAndCallback) msg.obj).mCallback;
2300             if (data == null || callback == null) {
2301                 return;
2302             }
2303
2304             final Bitmap bitmap;
2305             switch (data.getLocalDataType()) {
2306                 case LocalData.LOCAL_IN_PROGRESS_DATA:
2307                     byte[] jpegData = Storage.getJpegForSession(data.getUri());
2308                     if (jpegData != null) {
2309                         bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2310                     } else {
2311                         bitmap = null;
2312                     }
2313                     break;
2314
2315                 case LocalData.LOCAL_IMAGE:
2316                     FileInputStream stream;
2317                     try {
2318                         stream = new FileInputStream(data.getPath());
2319                     } catch (FileNotFoundException e) {
2320                         Log.e(TAG, "File not found:" + data.getPath());
2321                         return;
2322                     }
2323                     Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2324                             data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2325                             mAboveFilmstripControlLayout.getMeasuredHeight());
2326                     if (data.getRotation() % 180 != 0) {
2327                         int dummy = dim.x;
2328                         dim.x = dim.y;
2329                         dim.y = dummy;
2330                     }
2331                     bitmap = LocalDataUtil
2332                             .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2333                                     (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2334                                     data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2335                     break;
2336
2337                 case LocalData.LOCAL_VIDEO:
2338                     bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2339                     break;
2340
2341                 default:
2342                     bitmap = null;
2343                     break;
2344             }
2345
2346             if (bitmap == null) {
2347                 return;
2348             }
2349
2350             mMainHandler.post(new Runnable() {
2351                 @Override
2352                 public void run() {
2353                     callback.onCallback(bitmap);
2354                 }
2355             });
2356         }
2357     }
2358
2359     private void showDetailsDialog(int dataId) {
2360         final LocalData data = mDataAdapter.getLocalData(dataId);
2361         if (data == null) {
2362             return;
2363         }
2364         MediaDetails details = data.getMediaDetails(getAndroidContext());
2365         if (details == null) {
2366             return;
2367         }
2368         Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2369         detailDialog.show();
2370
2371         UsageStatistics.photoInteraction(
2372                 UsageStatistics.hashFileName(fileNameFromDataID(dataId)),
2373                 eventprotos.CameraEvent.InteractionType.DETAILS,
2374                 InteractionCause.BUTTON);
2375     }
2376
2377     /**
2378      * Show or hide action bar items depending on current data type.
2379      */
2380     private void updateActionBarMenu(LocalData data) {
2381         if (mActionBarMenu == null) {
2382             return;
2383         }
2384
2385         MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2386         if (detailsMenuItem == null) {
2387             return;
2388         }
2389
2390         int type = data.getLocalDataType();
2391         boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2392         detailsMenuItem.setVisible(showDetails);
2393     }
2394 }