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