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