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