CameraActivity.java revision 828081f5f947abb8930f084386492efde8c5b83b
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 screen turns off
284    private final BroadcastReceiver mScreenOffReceiver = 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
1454            // when screen is off.
1455            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1456            registerReceiver(mScreenOffReceiver, filter);
1457        }
1458        mCameraAppUI = new CameraAppUI(this,
1459                (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1460
1461        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1462
1463        mAboveFilmstripControlLayout =
1464                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1465
1466        // Add the session listener so we can track the session progress
1467        // updates.
1468        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1469        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1470        mFilmstripController.setImageGap(
1471                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1472        mPanoramaViewHelper = new PanoramaViewHelper(this);
1473        mPanoramaViewHelper.onCreate();
1474        // Set up the camera preview first so the preview shows up ASAP.
1475        mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder);
1476        mDataAdapter.setLocalDataListener(mLocalDataListener);
1477
1478        mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1479                mDataAdapter);
1480
1481        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1482        if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1483                                        Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1484            mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1485        }
1486
1487        mLocationManager = new LocationManager(mAppContext);
1488
1489        mOrientationManager = new OrientationManagerImpl(this);
1490        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1491
1492        setModuleFromModeIndex(getModeIndex());
1493        mCameraAppUI.prepareModuleUI();
1494        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1495
1496        if (!mSecureCamera) {
1497            mFilmstripController.setDataAdapter(mDataAdapter);
1498            if (!isCaptureIntent()) {
1499                mDataAdapter.requestLoad(new Callback<Void>() {
1500                    @Override
1501                    public void onCallback(Void result) {
1502                        fillTemporarySessions();
1503                    }
1504                });
1505            }
1506        } else {
1507            // Put a lock placeholder as the last image by setting its date to
1508            // 0.
1509            ImageView v = (ImageView) getLayoutInflater().inflate(
1510                    R.layout.secure_album_placeholder, null);
1511            v.setTag(R.id.mediadata_tag_viewtype, LocalDataViewType.SECURE_ALBUM_PLACEHOLDER.ordinal());
1512            v.setOnClickListener(new View.OnClickListener() {
1513                @Override
1514                public void onClick(View view) {
1515                    UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1516                            NavigationChange.InteractionCause.BUTTON);
1517                    startGallery();
1518                    finish();
1519                }
1520            });
1521            v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1522            mDataAdapter = new FixedLastDataAdapter(
1523                    mAppContext,
1524                    mDataAdapter,
1525                    new SimpleViewData(
1526                            v,
1527                            LocalDataViewType.SECURE_ALBUM_PLACEHOLDER,
1528                            v.getDrawable().getIntrinsicWidth(),
1529                            v.getDrawable().getIntrinsicHeight(),
1530                            0, 0));
1531            // Flush out all the original data.
1532            mDataAdapter.flush();
1533            mFilmstripController.setDataAdapter(mDataAdapter);
1534        }
1535
1536        setupNfcBeamPush();
1537
1538        mLocalImagesObserver = new LocalMediaObserver();
1539        mLocalVideosObserver = new LocalMediaObserver();
1540
1541        getContentResolver().registerContentObserver(
1542                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1543                mLocalImagesObserver);
1544        getContentResolver().registerContentObserver(
1545                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1546                mLocalVideosObserver);
1547        mMemoryManager = getServices().getMemoryManager();
1548
1549        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
1550            @Override
1551            public void run() {
1552                HashMap memoryData = mMemoryManager.queryMemory();
1553                UsageStatistics.instance().reportMemoryConsumed(memoryData,
1554                        MemoryQuery.REPORT_LABEL_LAUNCH);
1555            }
1556        });
1557        mMotionManager = getServices().getMotionManager();
1558    }
1559
1560    /**
1561     * Get the current mode index from the Intent or from persistent
1562     * settings.
1563     */
1564    public int getModeIndex() {
1565        int modeIndex = -1;
1566        int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1567        int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1568        int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1569        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1570                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1571            modeIndex = videoIndex;
1572        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1573            // Capture intent.
1574            modeIndex = photoIndex;
1575        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1576                ||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1577                        .getAction())
1578                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1579            modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1580                Keys.KEY_CAMERA_MODULE_LAST_USED);
1581
1582            // For upgraders who have not seen the aspect ratio selection screen,
1583            // we need to drop them back in the photo module and have them select
1584            // aspect ratio.
1585            // TODO: Move this to SettingsManager as an upgrade procedure.
1586            if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1587                    Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
1588                modeIndex = photoIndex;
1589            }
1590        } else {
1591            // If the activity has not been started using an explicit intent,
1592            // read the module index from the last time the user changed modes
1593            modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
1594                                                    Keys.KEY_STARTUP_MODULE_INDEX);
1595            if ((modeIndex == gcamIndex &&
1596                    !GcamHelper.hasGcamAsSeparateModule()) || modeIndex < 0) {
1597                modeIndex = photoIndex;
1598            }
1599        }
1600        return modeIndex;
1601    }
1602
1603    /**
1604     * Call this whenever the mode drawer or filmstrip change the visibility
1605     * state.
1606     */
1607    private void updatePreviewVisibility() {
1608        if (mCurrentModule == null) {
1609            return;
1610        }
1611
1612        int visibility = getPreviewVisibility();
1613        mCameraAppUI.onPreviewVisiblityChanged(visibility);
1614        updatePreviewRendering(visibility);
1615        mCurrentModule.onPreviewVisibilityChanged(visibility);
1616    }
1617
1618    private void updatePreviewRendering(int visibility) {
1619        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1620            mCameraAppUI.pausePreviewRendering();
1621        } else {
1622            mCameraAppUI.resumePreviewRendering();
1623        }
1624    }
1625
1626    private int getPreviewVisibility() {
1627        if (mFilmstripCoversPreview) {
1628            return ModuleController.VISIBILITY_HIDDEN;
1629        } else if (mModeListVisible){
1630            return ModuleController.VISIBILITY_COVERED;
1631        } else {
1632            return ModuleController.VISIBILITY_VISIBLE;
1633        }
1634    }
1635
1636    private void setRotationAnimation() {
1637        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1638        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1639        Window win = getWindow();
1640        WindowManager.LayoutParams winParams = win.getAttributes();
1641        winParams.rotationAnimation = rotationAnimation;
1642        win.setAttributes(winParams);
1643    }
1644
1645    @Override
1646    public void onUserInteraction() {
1647        super.onUserInteraction();
1648        if (!isFinishing()) {
1649            keepScreenOnForAWhile();
1650        }
1651    }
1652
1653    @Override
1654    public boolean dispatchTouchEvent(MotionEvent ev) {
1655        boolean result = super.dispatchTouchEvent(ev);
1656        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1657            // Real deletion is postponed until the next user interaction after
1658            // the gesture that triggers deletion. Until real deletion is
1659            // performed, users can click the undo button to bring back the
1660            // image that they chose to delete.
1661            if (mPendingDeletion && !mIsUndoingDeletion) {
1662                performDeletion();
1663            }
1664        }
1665        return result;
1666    }
1667
1668    @Override
1669    public void onPauseTasks() {
1670        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1671
1672        /*
1673         * Save the last module index after all secure camera and icon launches,
1674         * not just on mode switches.
1675         *
1676         * Right now we exclude capture intents from this logic, because we also
1677         * ignore the cross-Activity recovery logic in onStart for capture intents.
1678         */
1679        if (!isCaptureIntent()) {
1680            mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
1681                                 Keys.KEY_STARTUP_MODULE_INDEX,
1682                mCurrentModeIndex);
1683        }
1684
1685        mPaused = true;
1686        mPeekAnimationHandler = null;
1687        mPeekAnimationThread.quitSafely();
1688        mPeekAnimationThread = null;
1689
1690        // Delete photos that are pending deletion
1691        performDeletion();
1692        mCurrentModule.pause();
1693        mOrientationManager.pause();
1694        mPanoramaViewHelper.onPause();
1695
1696        mLocalImagesObserver.setForegroundChangeListener(null);
1697        mLocalImagesObserver.setActivityPaused(true);
1698        mLocalVideosObserver.setActivityPaused(true);
1699        mPreloader.cancelAllLoads();
1700        resetScreenOn();
1701
1702        mMotionManager.stop();
1703
1704        UsageStatistics.instance().backgrounded();
1705
1706        // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
1707        // button. Let's just kill the process.
1708        if (mCameraFatalError && !isFinishing()) {
1709            Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
1710            finish();
1711        } else {
1712            // Close the camera and wait for the operation done.
1713            mCameraController.closeCamera(true);
1714        }
1715    }
1716
1717    @Override
1718    public void onResumeTasks() {
1719        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1720        Log.v(TAG, "Build info: " + Build.DISPLAY);
1721
1722        mPaused = false;
1723        updateStorageSpaceAndHint(null);
1724
1725        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1726
1727        // TODO: Handle this in OrientationManager.
1728        // Auto-rotate off
1729        if (Settings.System.getInt(getContentResolver(),
1730                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1731            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1732            mAutoRotateScreen = false;
1733        } else {
1734            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1735            mAutoRotateScreen = true;
1736        }
1737
1738        // Foreground event logging.  ACTION_STILL_IMAGE_CAMERA and
1739        // INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
1740        // lockscreen onResume->onPause->onResume sequence.
1741        int source;
1742        String action = getIntent().getAction();
1743        if (action == null) {
1744            source = ForegroundSource.UNKNOWN_SOURCE;
1745        } else {
1746            switch (action) {
1747                case MediaStore.ACTION_IMAGE_CAPTURE:
1748                    source = ForegroundSource.ACTION_IMAGE_CAPTURE;
1749                    break;
1750                case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
1751                    // was UNKNOWN_SOURCE in Fishlake.
1752                    source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
1753                    break;
1754                case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
1755                    // was UNKNOWN_SOURCE in Fishlake.
1756                    source = ForegroundSource.ACTION_VIDEO_CAMERA;
1757                    break;
1758                case MediaStore.ACTION_VIDEO_CAPTURE:
1759                    source = ForegroundSource.ACTION_VIDEO_CAPTURE;
1760                    break;
1761                case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
1762                    // was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
1763                    source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
1764                    break;
1765                case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
1766                    source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
1767                    break;
1768                case Intent.ACTION_MAIN:
1769                    source = ForegroundSource.ACTION_MAIN;
1770                    break;
1771                default:
1772                    source = ForegroundSource.UNKNOWN_SOURCE;
1773                    break;
1774            }
1775        }
1776        UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
1777
1778        mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
1779        if (ApiHelper.isLOrHigher()) {
1780            // hide the up affordance for L devices, it's not very Materially
1781            mActionBar.setDisplayShowHomeEnabled(false);
1782        }
1783
1784        mOrientationManager.resume();
1785        mPeekAnimationThread = new HandlerThread("Peek animation");
1786        mPeekAnimationThread.start();
1787        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1788
1789        mCurrentModule.hardResetSettings(mSettingsManager);
1790        mCurrentModule.resume();
1791        UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1792                NavigationChange.InteractionCause.BUTTON);
1793        setSwipingEnabled(true);
1794
1795        if (!mResetToPreviewOnResume) {
1796            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1797            if (data != null) {
1798                mDataAdapter.refresh(data.getUri());
1799            }
1800        }
1801        // The share button might be disabled to avoid double tapping.
1802        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1803        // Default is showing the preview, unless disabled by explicitly
1804        // starting an activity we want to return from to the filmstrip rather
1805        // than the preview.
1806        mResetToPreviewOnResume = true;
1807
1808        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1809                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1810            if (!mSecureCamera) {
1811                // If it's secure camera, requestLoad() should not be called
1812                // as it will load all the data.
1813                if (!mFilmstripVisible) {
1814                    mDataAdapter.requestLoad(new Callback<Void>() {
1815                        @Override
1816                        public void onCallback(Void result) {
1817                            fillTemporarySessions();
1818                        }
1819                    });
1820                } else {
1821                    mDataAdapter.requestLoadNewPhotos();
1822                }
1823            }
1824        }
1825        mLocalImagesObserver.setActivityPaused(false);
1826        mLocalVideosObserver.setActivityPaused(false);
1827        if (!mSecureCamera) {
1828            mLocalImagesObserver.setForegroundChangeListener(
1829                    new LocalMediaObserver.ChangeListener() {
1830                @Override
1831                public void onChange() {
1832                    mDataAdapter.requestLoadNewPhotos();
1833                }
1834            });
1835        }
1836
1837        keepScreenOnForAWhile();
1838
1839        // Lights-out mode at all times.
1840        final View rootView = findViewById(R.id.activity_root_view);
1841        mLightsOutRunnable.run();
1842        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1843                new OnSystemUiVisibilityChangeListener() {
1844                    @Override
1845                    public void onSystemUiVisibilityChange(int visibility) {
1846                        mMainHandler.removeCallbacks(mLightsOutRunnable);
1847                        mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
1848                    }
1849                });
1850
1851        mPanoramaViewHelper.onResume();
1852        ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1853        syncLocationManagerSetting();
1854
1855        final int previewVisibility = getPreviewVisibility();
1856        updatePreviewRendering(previewVisibility);
1857
1858        mMotionManager.start();
1859    }
1860
1861    private void fillTemporarySessions() {
1862        if (mSecureCamera) {
1863            return;
1864        }
1865        // There might be sessions still in flight (processed by our service).
1866        // Make sure they're added to the filmstrip.
1867        getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1868    }
1869
1870    @Override
1871    public void onStartTasks() {
1872        mIsActivityRunning = true;
1873        mPanoramaViewHelper.onStart();
1874
1875        /*
1876         * If we're starting after launching a different Activity (lockscreen),
1877         * we need to use the last mode used in the other Activity, and
1878         * not the old one from this Activity.
1879         *
1880         * This needs to happen before CameraAppUI.resume() in order to set the
1881         * mode cover icon to the actual last mode used.
1882         *
1883         * Right now we exclude capture intents from this logic.
1884         */
1885        int modeIndex = getModeIndex();
1886        if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
1887            onModeSelected(modeIndex);
1888        }
1889
1890        if (mResetToPreviewOnResume) {
1891            mCameraAppUI.resume();
1892            mResetToPreviewOnResume = false;
1893        }
1894    }
1895
1896    @Override
1897    protected void onStopTasks() {
1898        mIsActivityRunning = false;
1899        mPanoramaViewHelper.onStop();
1900
1901        mLocationManager.disconnect();
1902    }
1903
1904    @Override
1905    public void onDestroyTasks() {
1906        if (mSecureCamera) {
1907            unregisterReceiver(mScreenOffReceiver);
1908        }
1909        mActionBar.removeOnMenuVisibilityListener(mOnMenuVisibilityListener);
1910        mSettingsManager.removeAllListeners();
1911        mCameraController.removeCallbackReceiver();
1912        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1913        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1914        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1915        mCameraAppUI.onDestroy();
1916        mModeListView.setVisibilityChangedListener(null);
1917        mCameraController = null;
1918        mSettingsManager = null;
1919        mOrientationManager = null;
1920        mButtonManager = null;
1921        mSoundPlayer.release();
1922        CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
1923        CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
1924    }
1925
1926    @Override
1927    public void onConfigurationChanged(Configuration config) {
1928        super.onConfigurationChanged(config);
1929        Log.v(TAG, "onConfigurationChanged");
1930        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1931            return;
1932        }
1933
1934        if (mLastLayoutOrientation != config.orientation) {
1935            mLastLayoutOrientation = config.orientation;
1936            mCurrentModule.onLayoutOrientationChanged(
1937                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1938        }
1939    }
1940
1941    @Override
1942    public boolean onKeyDown(int keyCode, KeyEvent event) {
1943        if (!mFilmstripVisible) {
1944            if (mCurrentModule.onKeyDown(keyCode, event)) {
1945                return true;
1946            }
1947            // Prevent software keyboard or voice search from showing up.
1948            if (keyCode == KeyEvent.KEYCODE_SEARCH
1949                    || keyCode == KeyEvent.KEYCODE_MENU) {
1950                if (event.isLongPress()) {
1951                    return true;
1952                }
1953            }
1954        }
1955
1956        return super.onKeyDown(keyCode, event);
1957    }
1958
1959    @Override
1960    public boolean onKeyUp(int keyCode, KeyEvent event) {
1961        if (!mFilmstripVisible) {
1962            // If a module is in the middle of capture, it should
1963            // consume the key event.
1964            if (mCurrentModule.onKeyUp(keyCode, event)) {
1965                return true;
1966            } else if (keyCode == KeyEvent.KEYCODE_MENU
1967                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1968                // Let the mode list view consume the event.
1969                mCameraAppUI.openModeList();
1970                return true;
1971            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1972                mCameraAppUI.showFilmstrip();
1973                return true;
1974            }
1975        } else {
1976            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1977                mFilmstripController.goToNextItem();
1978                return true;
1979            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1980                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1981                if (!wentToPrevious) {
1982                  // at beginning of filmstrip, hide and go back to preview
1983                  mCameraAppUI.hideFilmstrip();
1984                }
1985                return true;
1986            }
1987        }
1988        return super.onKeyUp(keyCode, event);
1989    }
1990
1991    @Override
1992    public void onBackPressed() {
1993        if (!mCameraAppUI.onBackPressed()) {
1994            if (!mCurrentModule.onBackPressed()) {
1995                super.onBackPressed();
1996            }
1997        }
1998    }
1999
2000    @Override
2001    public boolean isAutoRotateScreen() {
2002        // TODO: Move to OrientationManager.
2003        return mAutoRotateScreen;
2004    }
2005
2006    @Override
2007    public boolean onCreateOptionsMenu(Menu menu) {
2008        MenuInflater inflater = getMenuInflater();
2009        inflater.inflate(R.menu.filmstrip_menu, menu);
2010        mActionBarMenu = menu;
2011
2012        // add a button for launching the gallery
2013        if (mGalleryIntent != null) {
2014            CharSequence appName =  IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
2015            if (appName != null) {
2016                MenuItem menuItem = menu.add(appName);
2017                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2018                menuItem.setIntent(mGalleryIntent);
2019
2020                Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
2021                if (galleryLogo != null) {
2022                    menuItem.setIcon(galleryLogo);
2023                }
2024            }
2025        }
2026
2027        return super.onCreateOptionsMenu(menu);
2028    }
2029
2030    @Override
2031    public boolean onPrepareOptionsMenu(Menu menu) {
2032        if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
2033            // Compatibility pre-L: launching new activities right above
2034            // lockscreen does not reliably work, only show help if not secure
2035            menu.removeItem(R.id.action_help_and_feedback);
2036        }
2037
2038        return super.onPrepareOptionsMenu(menu);
2039    }
2040
2041    protected long getStorageSpaceBytes() {
2042        synchronized (mStorageSpaceLock) {
2043            return mStorageSpaceBytes;
2044        }
2045    }
2046
2047    protected interface OnStorageUpdateDoneListener {
2048        public void onStorageUpdateDone(long bytes);
2049    }
2050
2051    protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
2052        /*
2053         * We execute disk operations on a background thread in order to
2054         * free up the UI thread.  Synchronizing on the lock below ensures
2055         * that when getStorageSpaceBytes is called, the main thread waits
2056         * until this method has completed.
2057         *
2058         * However, .execute() does not ensure this execution block will be
2059         * run right away (.execute() schedules this AsyncTask for sometime
2060         * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
2061         * tries to execute the task in parellel with other AsyncTasks, but
2062         * there's still no guarantee).
2063         * e.g. don't call this then immediately call getStorageSpaceBytes().
2064         * Instead, pass in an OnStorageUpdateDoneListener.
2065         */
2066        (new AsyncTask<Void, Void, Long>() {
2067            @Override
2068            protected Long doInBackground(Void ... arg) {
2069                synchronized (mStorageSpaceLock) {
2070                    mStorageSpaceBytes = Storage.getAvailableSpace();
2071                    return mStorageSpaceBytes;
2072                }
2073            }
2074
2075            @Override
2076            protected void onPostExecute(Long bytes) {
2077                updateStorageHint(bytes);
2078                // This callback returns after I/O to check disk, so we could be
2079                // pausing and shutting down. If so, don't bother invoking.
2080                if (callback != null && !mPaused) {
2081                    callback.onStorageUpdateDone(bytes);
2082                } else {
2083                    Log.v(TAG, "ignoring storage callback after activity pause");
2084                }
2085            }
2086        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2087    }
2088
2089    protected void updateStorageHint(long storageSpace) {
2090        if (!mIsActivityRunning) {
2091            return;
2092        }
2093
2094        String message = null;
2095        if (storageSpace == Storage.UNAVAILABLE) {
2096            message = getString(R.string.no_storage);
2097        } else if (storageSpace == Storage.PREPARING) {
2098            message = getString(R.string.preparing_sd);
2099        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
2100            message = getString(R.string.access_sd_fail);
2101        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
2102            message = getString(R.string.spaceIsLow_content);
2103        }
2104
2105        if (message != null) {
2106            Log.w(TAG, "Storage warning: " + message);
2107            if (mStorageHint == null) {
2108                mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
2109            } else {
2110                mStorageHint.setText(message);
2111            }
2112            mStorageHint.show();
2113            UsageStatistics.instance().storageWarning(storageSpace);
2114
2115            // Disable all user interactions,
2116            mCameraAppUI.setDisableAllUserInteractions(true);
2117        } else if (mStorageHint != null) {
2118            mStorageHint.cancel();
2119            mStorageHint = null;
2120
2121            // Re-enable all user interactions.
2122            mCameraAppUI.setDisableAllUserInteractions(false);
2123        }
2124    }
2125
2126    protected void setResultEx(int resultCode) {
2127        mResultCodeForTesting = resultCode;
2128        setResult(resultCode);
2129    }
2130
2131    protected void setResultEx(int resultCode, Intent data) {
2132        mResultCodeForTesting = resultCode;
2133        mResultDataForTesting = data;
2134        setResult(resultCode, data);
2135    }
2136
2137    public int getResultCode() {
2138        return mResultCodeForTesting;
2139    }
2140
2141    public Intent getResultData() {
2142        return mResultDataForTesting;
2143    }
2144
2145    public boolean isSecureCamera() {
2146        return mSecureCamera;
2147    }
2148
2149    @Override
2150    public boolean isPaused() {
2151        return mPaused;
2152    }
2153
2154    @Override
2155    public int getPreferredChildModeIndex(int modeIndex) {
2156        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2157            boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
2158            if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule()) {
2159                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
2160            }
2161        }
2162        return modeIndex;
2163    }
2164
2165    @Override
2166    public void onModeSelected(int modeIndex) {
2167        if (mCurrentModeIndex == modeIndex) {
2168            return;
2169        }
2170
2171        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
2172        // Record last used camera mode for quick switching
2173        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
2174                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2175            mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2176                                 Keys.KEY_CAMERA_MODULE_LAST_USED,
2177                                 modeIndex);
2178        }
2179
2180        closeModule(mCurrentModule);
2181
2182        // Select the correct module index from the mode switcher index.
2183        modeIndex = getPreferredChildModeIndex(modeIndex);
2184        setModuleFromModeIndex(modeIndex);
2185
2186        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
2187        mCameraAppUI.addShutterListener(mCurrentModule);
2188        openModule(mCurrentModule);
2189        mCurrentModule.onOrientationChanged(mLastRawOrientation);
2190        // Store the module index so we can use it the next time the Camera
2191        // starts up.
2192        mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
2193                             Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
2194    }
2195
2196    /**
2197     * Shows the settings dialog.
2198     */
2199    @Override
2200    public void onSettingsSelected() {
2201        UsageStatistics.instance().controlUsed(
2202                eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
2203        Intent intent = new Intent(this, CameraSettingsActivity.class);
2204        startActivity(intent);
2205    }
2206
2207    @Override
2208    public void freezeScreenUntilPreviewReady() {
2209        mCameraAppUI.freezeScreenUntilPreviewReady();
2210    }
2211
2212    /**
2213     * Sets the mCurrentModuleIndex, creates a new module instance for the given
2214     * index an sets it as mCurrentModule.
2215     */
2216    private void setModuleFromModeIndex(int modeIndex) {
2217        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2218        if (agent == null) {
2219            return;
2220        }
2221        if (!agent.requestAppForCamera()) {
2222            mCameraController.closeCamera(true);
2223        }
2224        mCurrentModeIndex = agent.getModuleId();
2225        mCurrentModule = (CameraModule) agent.createModule(this);
2226    }
2227
2228    @Override
2229    public SettingsManager getSettingsManager() {
2230        return mSettingsManager;
2231    }
2232
2233    @Override
2234    public CameraServices getServices() {
2235        return (CameraServices) getApplication();
2236    }
2237
2238    public List<String> getSupportedModeNames() {
2239        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
2240        List<String> supported = new ArrayList<String>();
2241
2242        for (Integer modeIndex : indices) {
2243            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
2244            if (name != null && !name.equals("")) {
2245                supported.add(name);
2246            }
2247        }
2248        return supported;
2249    }
2250
2251    @Override
2252    public ButtonManager getButtonManager() {
2253        if (mButtonManager == null) {
2254            mButtonManager = new ButtonManager(this);
2255        }
2256        return mButtonManager;
2257    }
2258
2259    @Override
2260    public SoundPlayer getSoundPlayer() {
2261        return mSoundPlayer;
2262    }
2263
2264    /**
2265     * Creates an AlertDialog appropriate for choosing whether to enable
2266     * location on the first run of the app.
2267     */
2268    public AlertDialog getFirstTimeLocationAlert() {
2269        AlertDialog.Builder builder = new AlertDialog.Builder(this);
2270        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
2271            @Override
2272            public void onCallback(Boolean locationOn) {
2273                Keys.setLocation(mSettingsManager, locationOn, mLocationManager);
2274            }
2275        });
2276        if (builder != null) {
2277            return builder.create();
2278        } else {
2279            return null;
2280        }
2281    }
2282
2283    /**
2284     * Launches an ACTION_EDIT intent for the given local data item. If
2285     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
2286     * the user start either the tiny planet editor or another photo edior.
2287     *
2288     * @param data The data item to edit.
2289     */
2290    public void launchEditor(LocalData data) {
2291        Intent intent = new Intent(Intent.ACTION_EDIT)
2292                .setDataAndType(data.getUri(), data.getMimeType())
2293                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2294        try {
2295            launchActivityByIntent(intent);
2296        } catch (ActivityNotFoundException e) {
2297            final String msgEditWith = getResources().getString(R.string.edit_with);
2298            launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
2299        }
2300    }
2301
2302    @Override
2303    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2304        super.onCreateContextMenu(menu, v, menuInfo);
2305
2306        MenuInflater inflater = getMenuInflater();
2307        inflater.inflate(R.menu.filmstrip_context_menu, menu);
2308    }
2309
2310    @Override
2311    public boolean onContextItemSelected(MenuItem item) {
2312        switch (item.getItemId()) {
2313            case R.id.tiny_planet_editor:
2314                mMyFilmstripBottomControlListener.onTinyPlanet();
2315                return true;
2316            case R.id.photo_editor:
2317                mMyFilmstripBottomControlListener.onEdit();
2318                return true;
2319        }
2320        return false;
2321    }
2322
2323    /**
2324     * Launch the tiny planet editor.
2325     *
2326     * @param data The data must be a 360 degree stereographically mapped
2327     *            panoramic image. It will not be modified, instead a new item
2328     *            with the result will be added to the filmstrip.
2329     */
2330    public void launchTinyPlanetEditor(LocalData data) {
2331        TinyPlanetFragment fragment = new TinyPlanetFragment();
2332        Bundle bundle = new Bundle();
2333        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
2334        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
2335        fragment.setArguments(bundle);
2336        fragment.show(getFragmentManager(), "tiny_planet");
2337    }
2338
2339    /**
2340     * Returns what UI mode (capture mode or filmstrip) we are in.
2341     * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2342     */
2343    private int currentUserInterfaceMode() {
2344        int mode = NavigationChange.Mode.UNKNOWN_MODE;
2345        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2346            mode = NavigationChange.Mode.PHOTO_CAPTURE;
2347        }
2348        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2349            mode = NavigationChange.Mode.VIDEO_CAPTURE;
2350        }
2351        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2352            mode = NavigationChange.Mode.LENS_BLUR;
2353        }
2354        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2355            mode = NavigationChange.Mode.HDR_PLUS;
2356        }
2357        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2358            mode = NavigationChange.Mode.PHOTO_SPHERE;
2359        }
2360        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2361            mode = NavigationChange.Mode.PANORAMA;
2362        }
2363        if (mFilmstripVisible) {
2364            mode = NavigationChange.Mode.FILMSTRIP;
2365        }
2366        return mode;
2367    }
2368
2369    private void openModule(CameraModule module) {
2370        module.init(this, isSecureCamera(), isCaptureIntent());
2371        module.hardResetSettings(mSettingsManager);
2372        if (!mPaused) {
2373            module.resume();
2374            UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2375                    NavigationChange.InteractionCause.BUTTON);
2376            updatePreviewVisibility();
2377        }
2378    }
2379
2380    private void closeModule(CameraModule module) {
2381        module.pause();
2382        mCameraAppUI.clearModuleUI();
2383    }
2384
2385    private void performDeletion() {
2386        if (!mPendingDeletion) {
2387            return;
2388        }
2389        hideUndoDeletionBar(false);
2390        mDataAdapter.executeDeletion();
2391    }
2392
2393    public void showUndoDeletionBar() {
2394        if (mPendingDeletion) {
2395            performDeletion();
2396        }
2397        Log.v(TAG, "showing undo bar");
2398        mPendingDeletion = true;
2399        if (mUndoDeletionBar == null) {
2400            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2401                    mAboveFilmstripControlLayout, true);
2402            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2403            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2404            button.setOnClickListener(new View.OnClickListener() {
2405                @Override
2406                public void onClick(View view) {
2407                    mDataAdapter.undoDataRemoval();
2408                    hideUndoDeletionBar(true);
2409                }
2410            });
2411            // Setting undo bar clickable to avoid touch events going through
2412            // the bar to the buttons (eg. edit button, etc) underneath the bar.
2413            mUndoDeletionBar.setClickable(true);
2414            // When there is user interaction going on with the undo button, we
2415            // do not want to hide the undo bar.
2416            button.setOnTouchListener(new View.OnTouchListener() {
2417                @Override
2418                public boolean onTouch(View v, MotionEvent event) {
2419                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2420                        mIsUndoingDeletion = true;
2421                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2422                        mIsUndoingDeletion = false;
2423                    }
2424                    return false;
2425                }
2426            });
2427        }
2428        mUndoDeletionBar.setAlpha(0f);
2429        mUndoDeletionBar.setVisibility(View.VISIBLE);
2430        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2431    }
2432
2433    private void hideUndoDeletionBar(boolean withAnimation) {
2434        Log.v(TAG, "Hiding undo deletion bar");
2435        mPendingDeletion = false;
2436        if (mUndoDeletionBar != null) {
2437            if (withAnimation) {
2438                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2439                        .setListener(new Animator.AnimatorListener() {
2440                            @Override
2441                            public void onAnimationStart(Animator animation) {
2442                                // Do nothing.
2443                            }
2444
2445                            @Override
2446                            public void onAnimationEnd(Animator animation) {
2447                                mUndoDeletionBar.setVisibility(View.GONE);
2448                            }
2449
2450                            @Override
2451                            public void onAnimationCancel(Animator animation) {
2452                                // Do nothing.
2453                            }
2454
2455                            @Override
2456                            public void onAnimationRepeat(Animator animation) {
2457                                // Do nothing.
2458                            }
2459                        }).start();
2460            } else {
2461                mUndoDeletionBar.setVisibility(View.GONE);
2462            }
2463        }
2464    }
2465
2466    @Override
2467    public void onOrientationChanged(int orientation) {
2468        // We keep the last known orientation. So if the user first orient
2469        // the camera then point the camera to floor or sky, we still have
2470        // the correct orientation.
2471        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2472            return;
2473        }
2474        mLastRawOrientation = orientation;
2475        if (mCurrentModule != null) {
2476            mCurrentModule.onOrientationChanged(orientation);
2477        }
2478    }
2479
2480    /**
2481     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2482     * capture intent.
2483     *
2484     * @param enable {@code true} to enable swipe.
2485     */
2486    public void setSwipingEnabled(boolean enable) {
2487        // TODO: Bring back the functionality.
2488        if (isCaptureIntent()) {
2489            // lockPreview(true);
2490        } else {
2491            // lockPreview(!enable);
2492        }
2493    }
2494
2495    // Accessor methods for getting latency times used in performance testing
2496    public long getFirstPreviewTime() {
2497        if (mCurrentModule instanceof PhotoModule) {
2498            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2499            if (coverHiddenTime != -1) {
2500                return coverHiddenTime - mOnCreateTime;
2501            }
2502        }
2503        return -1;
2504    }
2505
2506    public long getAutoFocusTime() {
2507        return (mCurrentModule instanceof PhotoModule) ?
2508                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2509    }
2510
2511    public long getShutterLag() {
2512        return (mCurrentModule instanceof PhotoModule) ?
2513                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2514    }
2515
2516    public long getShutterToPictureDisplayedTime() {
2517        return (mCurrentModule instanceof PhotoModule) ?
2518                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2519    }
2520
2521    public long getPictureDisplayedToJpegCallbackTime() {
2522        return (mCurrentModule instanceof PhotoModule) ?
2523                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2524    }
2525
2526    public long getJpegCallbackFinishTime() {
2527        return (mCurrentModule instanceof PhotoModule) ?
2528                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2529    }
2530
2531    public long getCaptureStartTime() {
2532        return (mCurrentModule instanceof PhotoModule) ?
2533                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2534    }
2535
2536    public boolean isRecording() {
2537        return (mCurrentModule instanceof VideoModule) ?
2538                ((VideoModule) mCurrentModule).isRecording() : false;
2539    }
2540
2541    public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
2542        return mCameraController;
2543    }
2544
2545    // For debugging purposes only.
2546    public CameraModule getCurrentModule() {
2547        return mCurrentModule;
2548    }
2549
2550    @Override
2551    public void showTutorial(AbstractTutorialOverlay tutorial) {
2552        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2553    }
2554
2555    @Override
2556    public void showErrorAndFinish(int messageId) {
2557        CameraUtil.showErrorAndFinish(this, messageId);
2558    }
2559
2560    /**
2561     * Reads the current location recording settings and passes it on to the
2562     * location manager.
2563     */
2564    public void syncLocationManagerSetting() {
2565        Keys.syncLocationManager(mSettingsManager, mLocationManager);
2566    }
2567
2568    private void keepScreenOnForAWhile() {
2569        if (mKeepScreenOn) {
2570            return;
2571        }
2572        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2573        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2574        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2575    }
2576
2577    private void resetScreenOn() {
2578        mKeepScreenOn = false;
2579        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2580        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2581    }
2582
2583    /**
2584     * @return {@code true} if the Gallery is launched successfully.
2585     */
2586    private boolean startGallery() {
2587        if (mGalleryIntent == null) {
2588            return false;
2589        }
2590        try {
2591            UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2592                    NavigationChange.InteractionCause.BUTTON);
2593            Intent startGalleryIntent = new Intent(mGalleryIntent);
2594            int currentDataId = mFilmstripController.getCurrentId();
2595            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2596            if (currentLocalData != null) {
2597                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2598            }
2599            launchActivityByIntent(startGalleryIntent);
2600        } catch (ActivityNotFoundException e) {
2601            Log.w(TAG, "Failed to launch gallery activity, closing");
2602        }
2603        return false;
2604    }
2605
2606    private void setNfcBeamPushUriFromData(LocalData data) {
2607        final Uri uri = data.getUri();
2608        if (uri != Uri.EMPTY) {
2609            mNfcPushUris[0] = uri;
2610        } else {
2611            mNfcPushUris[0] = null;
2612        }
2613    }
2614
2615    /**
2616     * Updates the visibility of the filmstrip bottom controls and action bar.
2617     */
2618    private void updateUiByData(final int dataId) {
2619        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2620        if (currentData == null) {
2621            Log.w(TAG, "Current data ID not found.");
2622            hideSessionProgress();
2623            return;
2624        }
2625        updateActionBarMenu(currentData);
2626
2627        /* Bottom controls. */
2628        updateBottomControlsByData(currentData);
2629
2630        if (isSecureCamera()) {
2631            // We cannot show buttons in secure camera since go to other
2632            // activities might create a security hole.
2633            mCameraAppUI.getFilmstripBottomControls().hideControls();
2634            return;
2635        }
2636
2637
2638        setNfcBeamPushUriFromData(currentData);
2639
2640        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2641            mDataAdapter.updateMetadata(dataId);
2642        }
2643    }
2644
2645    /**
2646     * Updates the bottom controls based on the data.
2647     */
2648    private void updateBottomControlsByData(final LocalData currentData) {
2649
2650        final CameraAppUI.BottomPanel filmstripBottomPanel =
2651                mCameraAppUI.getFilmstripBottomControls();
2652        filmstripBottomPanel.showControls();
2653        filmstripBottomPanel.setEditButtonVisibility(
2654                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2655        filmstripBottomPanel.setShareButtonVisibility(
2656                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2657        filmstripBottomPanel.setDeleteButtonVisibility(
2658                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2659
2660        /* Progress bar */
2661
2662        Uri contentUri = currentData.getUri();
2663        CaptureSessionManager sessionManager = getServices()
2664                .getCaptureSessionManager();
2665
2666        if (sessionManager.hasErrorMessage(contentUri)) {
2667            showProcessError(sessionManager.getErrorMesage(contentUri));
2668        } else {
2669            filmstripBottomPanel.hideProgressError();
2670            CaptureSession session = sessionManager.getSession(contentUri);
2671
2672            if (session != null) {
2673                int sessionProgress = session.getProgress();
2674
2675                if (sessionProgress < 0) {
2676                    hideSessionProgress();
2677                } else {
2678                    CharSequence progressMessage = session.getProgressMessage();
2679                    showSessionProgress(progressMessage);
2680                    updateSessionProgress(sessionProgress);
2681                }
2682            } else {
2683                hideSessionProgress();
2684            }
2685        }
2686
2687        /* View button */
2688
2689        // We need to add this to a separate DB.
2690        final int viewButtonVisibility;
2691        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2692            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2693        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2694            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2695        } else {
2696            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2697        }
2698
2699        filmstripBottomPanel.setTinyPlanetEnabled(
2700                PanoramaMetadataLoader.isPanorama360(currentData));
2701        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2702    }
2703
2704    private class PeekAnimationHandler extends Handler {
2705        private class DataAndCallback {
2706            LocalData mData;
2707            com.android.camera.util.Callback<Bitmap> mCallback;
2708
2709            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2710                    callback) {
2711                mData = data;
2712                mCallback = callback;
2713            }
2714        }
2715
2716        public PeekAnimationHandler(Looper looper) {
2717            super(looper);
2718        }
2719
2720        /**
2721         * Starts the animation decoding job and posts a {@code Runnable} back
2722         * when when the decoding is done.
2723         *
2724         * @param data The data item to decode the thumbnail for.
2725         * @param callback {@link com.android.camera.util.Callback} after the
2726         *                 decoding is done.
2727         */
2728        public void startDecodingJob(final LocalData data,
2729                final com.android.camera.util.Callback<Bitmap> callback) {
2730            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2731                    new DataAndCallback(data, callback)).sendToTarget();
2732        }
2733
2734        @Override
2735        public void handleMessage(Message msg) {
2736            final LocalData data = ((DataAndCallback) msg.obj).mData;
2737            final com.android.camera.util.Callback<Bitmap> callback =
2738                    ((DataAndCallback) msg.obj).mCallback;
2739            if (data == null || callback == null) {
2740                return;
2741            }
2742
2743            final Bitmap bitmap;
2744            switch (data.getLocalDataType()) {
2745                case LocalData.LOCAL_IN_PROGRESS_DATA:
2746                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2747                    if (jpegData != null) {
2748                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2749                    } else {
2750                        bitmap = null;
2751                    }
2752                    break;
2753
2754                case LocalData.LOCAL_IMAGE:
2755                    FileInputStream stream;
2756                    try {
2757                        stream = new FileInputStream(data.getPath());
2758                    } catch (FileNotFoundException e) {
2759                        Log.e(TAG, "File not found:" + data.getPath());
2760                        return;
2761                    }
2762                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2763                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2764                            mAboveFilmstripControlLayout.getMeasuredHeight());
2765                    if (data.getRotation() % 180 != 0) {
2766                        int dummy = dim.x;
2767                        dim.x = dim.y;
2768                        dim.y = dummy;
2769                    }
2770                    bitmap = LocalDataUtil
2771                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2772                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2773                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2774                    break;
2775
2776                case LocalData.LOCAL_VIDEO:
2777                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2778                    break;
2779
2780                default:
2781                    bitmap = null;
2782                    break;
2783            }
2784
2785            if (bitmap == null) {
2786                return;
2787            }
2788
2789            mMainHandler.post(new Runnable() {
2790                @Override
2791                public void run() {
2792                    callback.onCallback(bitmap);
2793                }
2794            });
2795        }
2796    }
2797
2798    private void showDetailsDialog(int dataId) {
2799        final LocalData data = mDataAdapter.getLocalData(dataId);
2800        if (data == null) {
2801            return;
2802        }
2803        MediaDetails details = data.getMediaDetails(getAndroidContext());
2804        if (details == null) {
2805            return;
2806        }
2807        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2808        detailDialog.show();
2809        UsageStatistics.instance().mediaInteraction(
2810                fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS,
2811                NavigationChange.InteractionCause.BUTTON, fileAgeFromDataID(dataId));
2812    }
2813
2814    /**
2815     * Show or hide action bar items depending on current data type.
2816     */
2817    private void updateActionBarMenu(LocalData data) {
2818        if (mActionBarMenu == null) {
2819            return;
2820        }
2821
2822        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2823        if (detailsMenuItem == null) {
2824            return;
2825        }
2826
2827        int type = data.getLocalDataType();
2828        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2829        detailsMenuItem.setVisible(showDetails);
2830    }
2831}
2832