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