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