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