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