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