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