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