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