CameraActivity.java revision 4efa8b54c1df4e06f2d3caed2568015a737f9dda
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.content.ActivityNotFoundException;
25import android.content.BroadcastReceiver;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.content.pm.ActivityInfo;
32import android.content.res.Configuration;
33import android.graphics.Bitmap;
34import android.graphics.Color;
35import android.graphics.Matrix;
36import android.graphics.SurfaceTexture;
37import android.graphics.drawable.ColorDrawable;
38import android.net.Uri;
39import android.nfc.NfcAdapter;
40import android.nfc.NfcAdapter.CreateBeamUrisCallback;
41import android.nfc.NfcEvent;
42import android.os.Build;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Looper;
46import android.os.Message;
47import android.preference.PreferenceManager;
48import android.provider.MediaStore;
49import android.provider.Settings;
50import android.util.Log;
51import android.view.Gravity;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.MenuItem;
55import android.view.MotionEvent;
56import android.view.View;
57import android.view.ViewGroup;
58import android.view.Window;
59import android.view.WindowManager;
60import android.widget.FrameLayout;
61import android.widget.FrameLayout.LayoutParams;
62import android.widget.ImageView;
63import android.widget.PopupWindow;
64import android.widget.ProgressBar;
65import android.widget.ShareActionProvider;
66import android.widget.TextView;
67
68import com.android.camera.app.AppController;
69import com.android.camera.app.CameraAppUI;
70import com.android.camera.app.CameraController;
71import com.android.camera.app.CameraManager;
72import com.android.camera.app.CameraManagerFactory;
73import com.android.camera.app.CameraProvider;
74import com.android.camera.app.CameraServices;
75import com.android.camera.app.ModuleManagerImpl;
76import com.android.camera.app.OrientationManager;
77import com.android.camera.app.OrientationManagerImpl;
78import com.android.camera.data.CameraDataAdapter;
79import com.android.camera.data.FixedLastDataAdapter;
80import com.android.camera.data.InProgressDataWrapper;
81import com.android.camera.data.LocalData;
82import com.android.camera.data.LocalDataAdapter;
83import com.android.camera.data.LocalMediaObserver;
84import com.android.camera.data.PanoramaMetadataLoader;
85import com.android.camera.data.RgbzMetadataLoader;
86import com.android.camera.data.SimpleViewData;
87import com.android.camera.filmstrip.FilmstripContentPanel;
88import com.android.camera.filmstrip.FilmstripController;
89import com.android.camera.module.ModulesInfo;
90import com.android.camera.session.CaptureSessionManager;
91import com.android.camera.session.CaptureSessionManager.SessionListener;
92import com.android.camera.session.PlaceholderManager;
93import com.android.camera.settings.SettingsManager;
94import com.android.camera.settings.SettingsManager.SettingsCapabilities;
95import com.android.camera.tinyplanet.TinyPlanetFragment;
96import com.android.camera.ui.MainActivityLayout;
97import com.android.camera.ui.ModeListView;
98import com.android.camera.ui.PreviewStatusListener;
99import com.android.camera.ui.SettingsView;
100import com.android.camera.util.ApiHelper;
101import com.android.camera.util.CameraUtil;
102import com.android.camera.util.FeedbackHelper;
103import com.android.camera.util.GcamHelper;
104import com.android.camera.util.IntentHelper;
105import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
106import com.android.camera.util.UsageStatistics;
107import com.android.camera.widget.FilmstripView;
108import com.android.camera2.R;
109
110import java.io.File;
111import java.util.List;
112
113public class CameraActivity extends Activity
114        implements AppController, CameraManager.CameraOpenCallback,
115        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
116        OrientationManager.OnOrientationChangeListener {
117
118    private static final String TAG = "CAM_Activity";
119
120    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
121            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
122    public static final String ACTION_IMAGE_CAPTURE_SECURE =
123            "android.media.action.IMAGE_CAPTURE_SECURE";
124
125    // The intent extra for camera from secure lock screen. True if the gallery
126    // should only show newly captured pictures. sSecureAlbumId does not
127    // increment. This is used when switching between camera, camcorder, and
128    // panorama. If the extra is not set, it is in the normal camera mode.
129    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
130
131    /**
132     * Request code from an activity we started that indicated that we do not want
133     * to reset the view to the preview in onResume.
134     */
135    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
136
137    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
138
139    private static final int MSG_HIDE_ACTION_BAR = 1;
140    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
141    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000;  // 2 mins.
142
143    /**
144     * Whether onResume should reset the view to the preview.
145     */
146    private boolean mResetToPreviewOnResume = true;
147
148    /**
149     * This data adapter is used by FilmStripView.
150     */
151    private LocalDataAdapter mDataAdapter;
152
153    /**
154     * TODO: This should be moved to the app level.
155     */
156    private SettingsManager mSettingsManager;
157
158    /**
159     * TODO: This should be moved to the app level.
160     */
161    private SettingsController mSettingsController;
162    private ModeListView mModeListView;
163    private int mCurrentModeIndex;
164    private CameraModule mCurrentModule;
165    private ModuleManagerImpl mModuleManager;
166    private FrameLayout mAboveFilmstripControlLayout;
167    private FilmstripController mFilmstripController;
168    private boolean mFilmstripVisible;
169    private TextView mBottomProgressText;
170    private ProgressBar mBottomProgressBar;
171    private View mSessionProgressPanel;
172    private int mResultCodeForTesting;
173    private Intent mResultDataForTesting;
174    private OnScreenHint mStorageHint;
175    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
176    private boolean mAutoRotateScreen;
177    private boolean mSecureCamera;
178    private int mLastRawOrientation;
179    private OrientationManagerImpl mOrientationManager;
180    private LocationManager mLocationManager;
181    private ButtonManager mButtonManager;
182    private Handler mMainHandler;
183    private PanoramaViewHelper mPanoramaViewHelper;
184    private ActionBar mActionBar;
185    private ViewGroup mUndoDeletionBar;
186    private boolean mIsUndoingDeletion = false;
187
188    private final Uri[] mNfcPushUris = new Uri[1];
189
190    private LocalMediaObserver mLocalImagesObserver;
191    private LocalMediaObserver mLocalVideosObserver;
192
193    private boolean mPendingDeletion = false;
194
195    private CameraController mCameraController;
196    private boolean mPaused;
197    private CameraAppUI mCameraAppUI;
198
199    private FeedbackHelper mFeedbackHelper;
200
201    @Override
202    public CameraAppUI getCameraAppUI() {
203        return mCameraAppUI;
204    }
205
206    // close activity when screen turns off
207    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
208        @Override
209        public void onReceive(Context context, Intent intent) {
210            finish();
211        }
212    };
213
214    /**
215     * Whether the screen is kept turned on.
216     */
217    private boolean mKeepScreenOn;
218    private int mLastLayoutOrientation;
219    private final CameraAppUI.BottomControls.Listener mMyFilmstripBottomControlListener =
220            new CameraAppUI.BottomControls.Listener() {
221
222                /**
223                 * If the current photo is a photo sphere, this will launch the Photo Sphere
224                 * panorama viewer.
225                 */
226                @Override
227                public void onExternalViewer() {
228                    if (mPanoramaViewHelper == null) {
229                        return;
230                    }
231                    final LocalData data = getCurrentLocalData();
232                    if (data == null) {
233                        return;
234                    }
235                    final Uri contentUri = data.getContentUri();
236                    if (contentUri == Uri.EMPTY) {
237                        return;
238                    }
239
240                    if (PanoramaMetadataLoader.isPanorama(data)) {
241                        mPanoramaViewHelper.showPanorama(contentUri);
242                    } else if (RgbzMetadataLoader.hasRGBZData(data)) {
243                        mPanoramaViewHelper.showRgbz(contentUri);
244                    }
245                }
246
247                @Override
248                public void onEdit() {
249                    LocalData data = getCurrentLocalData();
250                    if (data == null) {
251                        return;
252                    }
253                    launchEditor(data);
254                }
255
256                @Override
257                public void onTinyPlanet() {
258                    LocalData data = getCurrentLocalData();
259                    if (data == null) {
260                        return;
261                    }
262                    final int currentDataId = getCurrentDataId();
263                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
264                            UsageStatistics.ACTION_EDIT, null, 0,
265                            UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
266                    launchTinyPlanetEditor(data);
267                }
268
269                @Override
270                public void onDelete() {
271                    final int currentDataId = getCurrentDataId();
272                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
273                            UsageStatistics.ACTION_DELETE, null, 0,
274                            UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
275                    removeData(currentDataId);
276                }
277
278                @Override
279                public void onShare() {
280                    final LocalData data = getCurrentLocalData();
281                    Intent shareIntent = getShareIntentByData(data);
282                    if (shareIntent != null) {
283                        try {
284                            launchActivityByIntent(shareIntent);
285                            mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
286                        } catch (ActivityNotFoundException ex) {
287                            // Nothing.
288                        }
289                    }
290                }
291
292                @Override
293                public void onGallery() {
294                    startGallery();
295                }
296
297                private int getCurrentDataId() {
298                    return mFilmstripController.getCurrentId();
299                }
300
301                private LocalData getCurrentLocalData() {
302                    return mDataAdapter.getLocalData(getCurrentDataId());
303                }
304
305                /**
306                 * Sets up the share intent and NFC properly according to the data.
307                 *
308                 * @param data The data to be shared.
309                 */
310                private Intent getShareIntentByData(final LocalData data) {
311                    Intent intent = null;
312                    final Uri contentUri = data.getContentUri();
313                    if (PanoramaMetadataLoader.isPanorama360(data) &&
314                            data.getContentUri() != Uri.EMPTY) {
315                        intent = new Intent(Intent.ACTION_SEND);
316                        intent.setType("application/vnd.google.panorama360+jpg");
317                        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
318                    } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
319                        final String mimeType = data.getMimeType();
320                        intent = getShareIntentFromType(mimeType);
321                        if (intent != null) {
322                            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
323                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
324                        }
325                    }
326                    return intent;
327                }
328
329                /**
330                 * Get the share intent according to the mimeType
331                 *
332                 * @param mimeType The mimeType of current data.
333                 * @return the video/image's ShareIntent or null if mimeType is invalid.
334                 */
335                private Intent getShareIntentFromType(String mimeType) {
336                    // Lazily create the intent object.
337                    Intent intent = new Intent(Intent.ACTION_SEND);
338                    if (mimeType.startsWith("video/")) {
339                        intent.setType("video/*");
340                    } else {
341                        if (mimeType.startsWith("image/")) {
342                            intent.setType("image/*");
343                        } else {
344                            Log.w(TAG, "unsupported mimeType " + mimeType);
345                        }
346                    }
347                    return intent;
348                }
349            };
350
351    @Override
352    public void onCameraOpened(CameraManager.CameraProxy camera) {
353        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
354            // We shouldn't be here. Just close the camera and leave.
355            camera.release(false);
356            throw new IllegalStateException("Camera opened but the module shouldn't be " +
357                    "requesting");
358        }
359        if (mCurrentModule != null) {
360            SettingsCapabilities capabilities =
361                SettingsController.getSettingsCapabilities(camera);
362            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
363            mCurrentModule.onCameraAvailable(camera);
364        }
365    }
366
367    @Override
368    public void onCameraDisabled(int cameraId) {
369        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_OPEN_FAIL,
370                "security");
371
372        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
373    }
374
375    @Override
376    public void onDeviceOpenFailure(int cameraId) {
377        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
378                UsageStatistics.ACTION_OPEN_FAIL, "open");
379
380        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
381    }
382
383    @Override
384    public void onReconnectionFailure(CameraManager mgr) {
385        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
386                UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
387
388        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
389    }
390
391    private class MainHandler extends Handler {
392        public MainHandler(Looper looper) {
393            super(looper);
394        }
395
396        @Override
397        public void handleMessage(Message msg) {
398            switch (msg.what) {
399                case MSG_HIDE_ACTION_BAR: {
400                    removeMessages(MSG_HIDE_ACTION_BAR);
401                    CameraActivity.this.setFilmstripUiVisibility(false);
402                    break;
403                }
404
405                case MSG_CLEAR_SCREEN_ON_FLAG:  {
406                    if (!mPaused) {
407                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
408                    }
409                    break;
410                }
411
412                default:
413            }
414        }
415    }
416
417    private String fileNameFromDataID(int dataID) {
418        final LocalData localData = mDataAdapter.getLocalData(dataID);
419
420        File localFile = new File(localData.getPath());
421        return localFile.getName();
422    }
423
424    private final FilmstripContentPanel.Listener mFilmstripListener =
425            new FilmstripContentPanel.Listener() {
426
427                @Override
428                public void onFilmstripHidden() {
429                    mFilmstripVisible = false;
430                    CameraActivity.this.setFilmstripUiVisibility(false);
431                    // When the user hide the filmstrip (either swipe out or
432                    // tap on back key) we move to the first item so next time
433                    // when the user swipe in the filmstrip, the most recent
434                    // one is shown.
435                    mFilmstripController.goToFirstItem();
436                    if (mCurrentModule != null) {
437                        mCurrentModule.onPreviewVisibilityChanged(true);
438                    }
439                }
440
441                @Override
442                public void onFilmstripShown() {
443                    mFilmstripVisible = true;
444                    updateUiByData(mFilmstripController.getCurrentId());
445                    if (mCurrentModule != null) {
446                        mCurrentModule.onPreviewVisibilityChanged(false);
447                    }
448                }
449
450                @Override
451                public void onDataPromoted(int dataID) {
452                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
453                            UsageStatistics.ACTION_DELETE, "promoted", 0,
454                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
455
456                    removeData(dataID);
457                }
458
459                @Override
460                public void onDataDemoted(int dataID) {
461                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
462                            UsageStatistics.ACTION_DELETE, "demoted", 0,
463                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
464
465                    removeData(dataID);
466                }
467
468                @Override
469                public void onEnterFullScreen(int dataId) {
470                    if (mFilmstripVisible) {
471                        CameraActivity.this.setFilmstripUiVisibility(false);
472                    }
473                }
474
475                @Override
476                public void onLeaveFullScreen(int dataId) {
477                    // Do nothing.
478                }
479
480                @Override
481                public void onEnterFilmstrip(int dataId) {
482                    if (mFilmstripVisible) {
483                        CameraActivity.this.setFilmstripUiVisibility(true);
484                    }
485                }
486
487                @Override
488                public void onLeaveFilmstrip(int dataId) {
489                    // Do nothing.
490                }
491
492                @Override
493                public void onDataReloaded() {
494                    if (!mFilmstripVisible) {
495                        return;
496                    }
497                    updateUiByData(mFilmstripController.getCurrentId());
498                }
499
500                @Override
501                public void onDataUpdated(int dataId) {
502                    if (!mFilmstripVisible) {
503                        return;
504                    }
505                    updateUiByData(mFilmstripController.getCurrentId());
506                }
507
508                @Override
509                public void onEnterZoomView(int dataID) {
510                    if (mFilmstripVisible) {
511                        CameraActivity.this.setFilmstripUiVisibility(false);
512                    }
513                }
514
515                @Override
516                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
517                    if (!mFilmstripVisible) {
518                        return;
519                    }
520                    // TODO: This callback is UI event callback, should always
521                    // happen on UI thread. Find the reason for this
522                    // runOnUiThread() and fix it.
523                    runOnUiThread(new Runnable() {
524                        @Override
525                        public void run() {
526                            updateUiByData(newDataId);
527                        }
528                    });
529                }
530            };
531
532    private LocalDataAdapter.LocalDataListener mLocalDataListener =
533            new LocalDataAdapter.LocalDataListener() {
534                @Override
535                public void onMetadataUpdated(List<Integer> updatedData) {
536                    int currentDataId = mFilmstripController.getCurrentId();
537                    for (Integer dataId : updatedData) {
538                        if (dataId == currentDataId) {
539                            updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
540                        }
541                    }
542                }
543            };
544
545    public void gotoGallery() {
546        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP,
547                "thumbnailTap");
548
549        mFilmstripController.goToNextItem();
550    }
551
552    /**
553     * If {@param visible} is false, this hides the action bar and switches the
554     * filmstrip UI to lights-out mode.
555     */
556    // TODO: This should not be called outside of the activity.
557    public void setFilmstripUiVisibility(boolean visible) {
558        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
559
560        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
561        int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE :
562                        View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
563        if (newSystemUIVisibility != currentSystemUIVisibility) {
564            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
565        }
566
567        boolean currentActionBarVisibility = mActionBar.isShowing();
568        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
569        if (visible != currentActionBarVisibility) {
570            if (visible) {
571                mActionBar.show();
572            } else {
573                mActionBar.hide();
574            }
575        }
576    }
577
578    private void hideSessionProgress() {
579        mSessionProgressPanel.setVisibility(View.GONE);
580    }
581
582    private void showSessionProgress(CharSequence message) {
583        mBottomProgressText.setText(message);
584        mSessionProgressPanel.setVisibility(View.VISIBLE);
585    }
586
587    private void updateSessionProgress(int progress) {
588        mBottomProgressBar.setProgress(progress);
589    }
590
591    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
592    private void setupNfcBeamPush() {
593        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this);
594        if (adapter == null) {
595            return;
596        }
597
598        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
599            // Disable beaming
600            adapter.setNdefPushMessage(null, CameraActivity.this);
601            return;
602        }
603
604        adapter.setBeamPushUris(null, CameraActivity.this);
605        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
606            @Override
607            public Uri[] createBeamUris(NfcEvent event) {
608                return mNfcPushUris;
609            }
610        }, CameraActivity.this);
611    }
612
613    @Override
614    public void onMenuVisibilityChanged(boolean isVisible) {
615        // TODO: Remove this or bring back the original implementation: cancel
616        // auto-hide actionbar.
617    }
618
619    @Override
620    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
621        int currentDataId = mFilmstripController.getCurrentId();
622        if (currentDataId < 0) {
623            return false;
624        }
625        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE,
626                intent.getComponent().getPackageName(), 0,
627                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
628        return true;
629    }
630
631    // Note: All callbacks come back on the main thread.
632    private final SessionListener mSessionListener =
633            new SessionListener() {
634                @Override
635                public void onSessionQueued(final Uri uri) {
636                    notifyNewMedia(uri);
637                    int dataID = mDataAdapter.findDataByContentUri(uri);
638                    if (dataID != -1) {
639                        // Don't allow special UI actions (swipe to
640                        // delete, for example) on in-progress data.
641                        LocalData d = mDataAdapter.getLocalData(dataID);
642                        InProgressDataWrapper newData = new InProgressDataWrapper(d);
643                        mDataAdapter.updateData(dataID, newData);
644                    }
645                }
646
647                @Override
648                public void onSessionDone(final Uri uri) {
649                    Log.v(TAG, "onSessionDone:" + uri);
650                    int doneID = mDataAdapter.findDataByContentUri(uri);
651                    int currentDataId = mFilmstripController.getCurrentId();
652
653                    if (currentDataId == doneID) {
654                        hideSessionProgress();
655                        updateSessionProgress(0);
656                    }
657                    mDataAdapter.refresh(CameraActivity.this, uri, /* isInProgress */ false);
658                }
659
660                @Override
661                public void onSessionProgress(final Uri uri, final int progress) {
662                    if (progress < 0) {
663                        // Do nothing, there is no task for this URI.
664                        return;
665                    }
666                    int currentDataId = mFilmstripController.getCurrentId();
667                    if (currentDataId == -1) {
668                        return;
669                    }
670                    if (uri.equals(
671                            mDataAdapter.getLocalData(currentDataId).getContentUri())) {
672                        updateSessionProgress(progress);
673                    }
674                }
675
676                @Override
677                public void onSessionUpdated(Uri uri) {
678                    mDataAdapter.refresh(CameraActivity.this, uri, /* isInProgress */ true);
679                }
680            };
681
682    @Override
683    public Context getAndroidContext() {
684        return this;
685    }
686
687    @Override
688    public void launchActivityByIntent(Intent intent) {
689        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
690    }
691
692    @Override
693    public int getCurrentModuleIndex() {
694        return mCurrentModeIndex;
695    }
696
697    @Override
698    public SurfaceTexture getPreviewBuffer() {
699        // TODO: implement this
700        return null;
701    }
702
703    @Override
704    public void onPreviewStarted() {
705        mCameraAppUI.onPreviewStarted();
706    }
707
708    @Override
709    public void updatePreviewAspectRatio(float aspectRatio) {
710        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
711    }
712
713    @Override
714    public void updatePreviewTransform(Matrix matrix) {
715        mCameraAppUI.updatePreviewTransform(matrix);
716    }
717
718    @Override
719    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
720        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
721    }
722
723    @Override
724    public FrameLayout getModuleLayoutRoot() {
725        return mCameraAppUI.getModuleRootView();
726    }
727
728    @Override
729    public void setShutterEventsListener(ShutterEventsListener listener) {
730        // TODO: implement this
731    }
732
733    @Override
734    public void setShutterEnabled(boolean enabled) {
735        // TODO: implement this
736    }
737
738    @Override
739    public boolean isShutterEnabled() {
740        // TODO: implement this
741        return false;
742    }
743
744    @Override
745    public void startPreCaptureAnimation() {
746        mCameraAppUI.startPreCaptureAnimation();
747    }
748
749    @Override
750    public void cancelPreCaptureAnimation() {
751        // TODO: implement this
752    }
753
754    @Override
755    public void startPostCaptureAnimation() {
756        // TODO: implement this
757    }
758
759    @Override
760    public void startPostCaptureAnimation(Bitmap thumbnail) {
761        // TODO: implement this
762    }
763
764    @Override
765    public void cancelPostCaptureAnimation() {
766        // TODO: implement this
767    }
768
769    @Override
770    public OrientationManager getOrientationManager() {
771        return mOrientationManager;
772    }
773
774    @Override
775    public LocationManager getLocationManager() {
776        return mLocationManager;
777    }
778
779    @Override
780    public void lockOrientation() {
781        mOrientationManager.lockOrientation();
782    }
783
784    @Override
785    public void unlockOrientation() {
786        mOrientationManager.unlockOrientation();
787    }
788
789    @Override
790    public void notifyNewMedia(Uri uri) {
791        ContentResolver cr = getContentResolver();
792        String mimeType = cr.getType(uri);
793        if (mimeType.startsWith("video/")) {
794            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
795            mDataAdapter.addNewVideo(this, uri);
796        } else if (mimeType.startsWith("image/")) {
797            CameraUtil.broadcastNewPicture(this, uri);
798            mDataAdapter.addNewPhoto(this, uri);
799        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
800            mDataAdapter.addNewPhoto(this, uri);
801        } else {
802            android.util.Log.w(TAG, "Unknown new media with MIME type:"
803                    + mimeType + ", uri:" + uri);
804        }
805    }
806
807    @Override
808    public void enableKeepScreenOn(boolean enabled) {
809        if (mPaused) {
810            return;
811        }
812
813        mKeepScreenOn = enabled;
814        if (mKeepScreenOn) {
815            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
816            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
817        } else {
818            keepScreenOnForAWhile();
819        }
820    }
821
822    @Override
823    public CameraProvider getCameraProvider() {
824        return mCameraController;
825    }
826
827    private void removeData(int dataID) {
828        mDataAdapter.removeData(CameraActivity.this, dataID);
829        if (mDataAdapter.getTotalNumber() > 1) {
830            showUndoDeletionBar();
831        } else {
832            // If camera preview is the only view left in filmstrip,
833            // no need to show undo bar.
834            mPendingDeletion = true;
835            performDeletion();
836        }
837    }
838
839    @Override
840    public boolean onOptionsItemSelected(MenuItem item) {
841        // Handle presses on the action bar items
842        switch (item.getItemId()) {
843            case android.R.id.home:
844                onBackPressed();
845                return true;
846            default:
847                return super.onOptionsItemSelected(item);
848        }
849    }
850
851    private boolean isCaptureIntent() {
852        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
853                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
854                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
855            return true;
856        } else {
857            return false;
858        }
859    }
860
861    @Override
862    public void onCreate(Bundle state) {
863        super.onCreate(state);
864        GcamHelper.init(getContentResolver());
865
866        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
867        setContentView(R.layout.activity_main);
868        mActionBar = getActionBar();
869        mActionBar.addOnMenuVisibilityListener(this);
870        mMainHandler = new MainHandler(getMainLooper());
871        mCameraController =
872                new CameraController(this, this, mMainHandler,
873                        CameraManagerFactory.getAndroidCameraManager());
874        ComboPreferences prefs = new ComboPreferences(getAndroidContext());
875
876        mSettingsManager = new SettingsManager(this, null, mCameraController.getNumberOfCameras());
877        // Remove this after we get rid of ComboPreferences.
878        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
879        prefs.setLocalId(this, cameraId);
880        CameraSettings.upgradeGlobalPreferences(prefs, mCameraController.getNumberOfCameras());
881        // TODO: Try to move all the resources allocation to happen as soon as
882        // possible so we can call module.init() at the earliest time.
883        mModuleManager = new ModuleManagerImpl();
884        ModulesInfo.setupModules(this, mModuleManager);
885
886        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
887        mModeListView.init(mModuleManager.getSupportedModeIndexList());
888        if (ApiHelper.HAS_ROTATION_ANIMATION) {
889            setRotationAnimation();
890        }
891
892        // Check if this is in the secure camera mode.
893        Intent intent = getIntent();
894        String action = intent.getAction();
895        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
896                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
897            mSecureCamera = true;
898        } else {
899            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
900        }
901
902        if (mSecureCamera) {
903            // Change the window flags so that secure camera can show when locked
904            Window win = getWindow();
905            WindowManager.LayoutParams params = win.getAttributes();
906            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
907            win.setAttributes(params);
908
909            // Filter for screen off so that we can finish secure camera activity
910            // when screen is off.
911            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
912            registerReceiver(mScreenOffReceiver, filter);
913        }
914        mCameraAppUI = new CameraAppUI(this,
915                (MainActivityLayout) findViewById(R.id.activity_root_view),
916                isSecureCamera(), isCaptureIntent());
917
918        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
919
920        mAboveFilmstripControlLayout =
921                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
922
923        // Add the session listener so we can track the session progress updates.
924        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
925        mSessionProgressPanel = findViewById(R.id.pano_session_progress_panel);
926        mBottomProgressBar = (ProgressBar) findViewById(R.id.pano_session_progress_bar);
927        mBottomProgressText = (TextView) findViewById(R.id.pano_session_progress_text);
928        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
929        mFilmstripController.setImageGap(
930                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
931        mPanoramaViewHelper = new PanoramaViewHelper(this);
932        mPanoramaViewHelper.onCreate();
933        // Set up the camera preview first so the preview shows up ASAP.
934        mDataAdapter = new CameraDataAdapter(
935                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
936        mDataAdapter.setLocalDataListener(mLocalDataListener);
937
938        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
939
940        mLocationManager = new LocationManager(this,
941            new LocationManager.Listener() {
942                @Override
943                public void showGpsOnScreenIndicator(boolean hasSignal) {
944                }
945
946                @Override
947                public void hideGpsOnScreenIndicator() {
948                }
949            });
950
951        mSettingsController = new SettingsController(this, mSettingsManager, mLocationManager);
952
953        int modeIndex = -1;
954        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
955                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
956            modeIndex = ModeListView.MODE_VIDEO;
957        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
958                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
959                        .getAction())) {
960            modeIndex = ModeListView.MODE_PHOTO;
961            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
962                        == ModeListView.MODE_GCAM && GcamHelper.hasGcamCapture()) {
963                modeIndex = ModeListView.MODE_GCAM;
964            }
965        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
966                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
967            modeIndex = ModeListView.MODE_PHOTO;
968        } else {
969            // If the activity has not been started using an explicit intent,
970            // read the module index from the last time the user changed modes
971            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
972            if ((modeIndex == ModeListView.MODE_GCAM &&
973                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
974                modeIndex = ModeListView.MODE_PHOTO;
975            }
976        }
977
978        mOrientationManager = new OrientationManagerImpl(this);
979        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
980
981        setModuleFromModeIndex(modeIndex);
982
983        // TODO: Remove this when refactor is done.
984        if (modeIndex == ModulesInfo.MODULE_PHOTO
985                || modeIndex == ModulesInfo.MODULE_VIDEO
986                || modeIndex == ModulesInfo.MODULE_GCAM
987                || modeIndex == ModulesInfo.MODULE_CRAFT
988                || modeIndex == ModulesInfo.MODULE_REFOCUS) {
989            mCameraAppUI.prepareModuleUI();
990        }
991        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
992
993        if (!mSecureCamera) {
994            mFilmstripController.setDataAdapter(mDataAdapter);
995            if (!isCaptureIntent()) {
996                mDataAdapter.requestLoad(this);
997            }
998        } else {
999            // Put a lock placeholder as the last image by setting its date to
1000            // 0.
1001            ImageView v = (ImageView) getLayoutInflater().inflate(
1002                    R.layout.secure_album_placeholder, null);
1003            v.setOnClickListener(new View.OnClickListener() {
1004                @Override
1005                public void onClick(View view) {
1006                    startGallery();
1007                    finish();
1008                }
1009            });
1010            mDataAdapter = new FixedLastDataAdapter(
1011                    mDataAdapter,
1012                    new SimpleViewData(
1013                            v,
1014                            v.getDrawable().getIntrinsicWidth(),
1015                            v.getDrawable().getIntrinsicHeight(),
1016                            0, 0));
1017            // Flush out all the original data.
1018            mDataAdapter.flush();
1019            mFilmstripController.setDataAdapter(mDataAdapter);
1020        }
1021
1022        setupNfcBeamPush();
1023
1024        mLocalImagesObserver = new LocalMediaObserver();
1025        mLocalVideosObserver = new LocalMediaObserver();
1026
1027        getContentResolver().registerContentObserver(
1028                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1029                mLocalImagesObserver);
1030        getContentResolver().registerContentObserver(
1031                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1032                mLocalVideosObserver);
1033        if (FeedbackHelper.feedbackAvailable()) {
1034            mFeedbackHelper = new FeedbackHelper(this);
1035        }
1036    }
1037
1038    private void setRotationAnimation() {
1039        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1040        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1041        Window win = getWindow();
1042        WindowManager.LayoutParams winParams = win.getAttributes();
1043        winParams.rotationAnimation = rotationAnimation;
1044        win.setAttributes(winParams);
1045    }
1046
1047    @Override
1048    public void onUserInteraction() {
1049        super.onUserInteraction();
1050        if (!isFinishing()) {
1051            keepScreenOnForAWhile();
1052        }
1053    }
1054
1055    @Override
1056    public boolean dispatchTouchEvent(MotionEvent ev) {
1057        boolean result = super.dispatchTouchEvent(ev);
1058        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1059            // Real deletion is postponed until the next user interaction after
1060            // the gesture that triggers deletion. Until real deletion is performed,
1061            // users can click the undo button to bring back the image that they
1062            // chose to delete.
1063            if (mPendingDeletion && !mIsUndoingDeletion) {
1064                performDeletion();
1065            }
1066        }
1067        return result;
1068    }
1069
1070    @Override
1071    public void onPause() {
1072        mPaused = true;
1073
1074        // Delete photos that are pending deletion
1075        performDeletion();
1076        mCurrentModule.pause();
1077        mOrientationManager.pause();
1078        // Close the camera and wait for the operation done.
1079        mCameraController.closeCamera();
1080
1081        mLocalImagesObserver.setActivityPaused(true);
1082        mLocalVideosObserver.setActivityPaused(true);
1083        resetScreenOn();
1084        super.onPause();
1085    }
1086
1087    @Override
1088    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1089        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1090            mResetToPreviewOnResume = false;
1091        } else {
1092            super.onActivityResult(requestCode, resultCode, data);
1093        }
1094    }
1095
1096    @Override
1097    public void onResume() {
1098        mPaused = false;
1099
1100        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1101
1102        // TODO: Handle this in OrientationManager.
1103        // Auto-rotate off
1104        if (Settings.System.getInt(getContentResolver(),
1105                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1106            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1107            mAutoRotateScreen = false;
1108        } else {
1109            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1110            mAutoRotateScreen = true;
1111        }
1112
1113        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1114                UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
1115
1116        mOrientationManager.resume();
1117        super.onResume();
1118        mCurrentModule.resume();
1119        setSwipingEnabled(true);
1120
1121        if (mResetToPreviewOnResume) {
1122            mCameraAppUI.resume();
1123        }
1124        // The share button might be disabled to avoid double tapping.
1125        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1126        // Default is showing the preview, unless disabled by explicitly
1127        // starting an activity we want to return from to the filmstrip rather
1128        // than the preview.
1129        mResetToPreviewOnResume = true;
1130
1131        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1132                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1133            if (!mSecureCamera) {
1134                // If it's secure camera, requestLoad() should not be called
1135                // as it will load all the data.
1136                if (!mFilmstripVisible) {
1137                    mDataAdapter.requestLoad(this);
1138                }
1139            }
1140        }
1141        mLocalImagesObserver.setActivityPaused(false);
1142        mLocalVideosObserver.setActivityPaused(false);
1143
1144        keepScreenOnForAWhile();
1145    }
1146
1147    @Override
1148    public void onStart() {
1149        super.onStart();
1150        mPanoramaViewHelper.onStart();
1151    }
1152
1153    @Override
1154    protected void onStop() {
1155        mPanoramaViewHelper.onStop();
1156        if (mFeedbackHelper != null) {
1157            mFeedbackHelper.stopFeedback();
1158        }
1159
1160        CameraManagerFactory.recycle();
1161        super.onStop();
1162    }
1163
1164    @Override
1165    public void onDestroy() {
1166        if (mSecureCamera) {
1167            unregisterReceiver(mScreenOffReceiver);
1168        }
1169        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1170        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1171        super.onDestroy();
1172    }
1173
1174    @Override
1175    public void onConfigurationChanged(Configuration config) {
1176        super.onConfigurationChanged(config);
1177        Log.v(TAG, "onConfigurationChanged");
1178        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1179            return;
1180        }
1181
1182        if (mLastLayoutOrientation != config.orientation) {
1183            mLastLayoutOrientation = config.orientation;
1184            mCurrentModule.onLayoutOrientationChanged(
1185                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1186        }
1187    }
1188
1189    @Override
1190    public boolean onKeyDown(int keyCode, KeyEvent event) {
1191        if (mFilmstripController.inCameraFullscreen()) {
1192            if (mCurrentModule.onKeyDown(keyCode, event)) {
1193                return true;
1194            }
1195            // Prevent software keyboard or voice search from showing up.
1196            if (keyCode == KeyEvent.KEYCODE_SEARCH
1197                    || keyCode == KeyEvent.KEYCODE_MENU) {
1198                if (event.isLongPress()) {
1199                    return true;
1200                }
1201            }
1202        }
1203
1204        return super.onKeyDown(keyCode, event);
1205    }
1206
1207    @Override
1208    public boolean onKeyUp(int keyCode, KeyEvent event) {
1209        if (mFilmstripController.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
1210            return true;
1211        }
1212        return super.onKeyUp(keyCode, event);
1213    }
1214
1215    @Override
1216    public void onBackPressed() {
1217        if (!mCameraAppUI.onBackPressed()) {
1218            if (!mCurrentModule.onBackPressed()) {
1219                super.onBackPressed();
1220            }
1221        }
1222    }
1223
1224    public boolean isAutoRotateScreen() {
1225        return mAutoRotateScreen;
1226    }
1227
1228    protected void updateStorageSpace() {
1229        mStorageSpaceBytes = Storage.getAvailableSpace();
1230    }
1231
1232    protected long getStorageSpaceBytes() {
1233        return mStorageSpaceBytes;
1234    }
1235
1236    protected void updateStorageSpaceAndHint() {
1237        updateStorageSpace();
1238        updateStorageHint(mStorageSpaceBytes);
1239    }
1240
1241    protected void updateStorageHint(long storageSpace) {
1242        String message = null;
1243        if (storageSpace == Storage.UNAVAILABLE) {
1244            message = getString(R.string.no_storage);
1245        } else if (storageSpace == Storage.PREPARING) {
1246            message = getString(R.string.preparing_sd);
1247        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1248            message = getString(R.string.access_sd_fail);
1249        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1250            message = getString(R.string.spaceIsLow_content);
1251        }
1252
1253        if (message != null) {
1254            if (mStorageHint == null) {
1255                mStorageHint = OnScreenHint.makeText(this, message);
1256            } else {
1257                mStorageHint.setText(message);
1258            }
1259            mStorageHint.show();
1260        } else if (mStorageHint != null) {
1261            mStorageHint.cancel();
1262            mStorageHint = null;
1263        }
1264    }
1265
1266    protected void setResultEx(int resultCode) {
1267        mResultCodeForTesting = resultCode;
1268        setResult(resultCode);
1269    }
1270
1271    protected void setResultEx(int resultCode, Intent data) {
1272        mResultCodeForTesting = resultCode;
1273        mResultDataForTesting = data;
1274        setResult(resultCode, data);
1275    }
1276
1277    public int getResultCode() {
1278        return mResultCodeForTesting;
1279    }
1280
1281    public Intent getResultData() {
1282        return mResultDataForTesting;
1283    }
1284
1285    public boolean isSecureCamera() {
1286        return mSecureCamera;
1287    }
1288
1289    @Override
1290    public boolean isPaused() {
1291        return mPaused;
1292    }
1293
1294    @Override
1295    public void onModeSelected(int modeIndex) {
1296        if (mCurrentModeIndex == modeIndex) {
1297            return;
1298        }
1299
1300        if (modeIndex == ModeListView.MODE_SETTING) {
1301            onSettingsSelected();
1302            return;
1303        }
1304
1305        CameraHolder.instance().keep();
1306        closeModule(mCurrentModule);
1307        int oldModuleIndex = mCurrentModeIndex;
1308        setModuleFromModeIndex(modeIndex);
1309
1310        // TODO: The following check is temporary for modules attached to the
1311        // generic_module layout. When the refactor is done, similar logic will
1312        // be applied to all modules.
1313        if (mCurrentModeIndex == ModulesInfo.MODULE_PHOTO
1314                || mCurrentModeIndex == ModulesInfo.MODULE_VIDEO
1315                || mCurrentModeIndex == ModulesInfo.MODULE_GCAM
1316                || mCurrentModeIndex == ModulesInfo.MODULE_CRAFT
1317                || mCurrentModeIndex == ModulesInfo.MODULE_REFOCUS) {
1318            if (oldModuleIndex != ModulesInfo.MODULE_PHOTO
1319                    && oldModuleIndex != ModulesInfo.MODULE_VIDEO
1320                    && oldModuleIndex != ModulesInfo.MODULE_GCAM
1321                    && oldModuleIndex != ModulesInfo.MODULE_CRAFT
1322                    && oldModuleIndex != ModulesInfo.MODULE_REFOCUS) {
1323                mCameraAppUI.prepareModuleUI();
1324            } else {
1325                mCameraAppUI.clearModuleUI();
1326            }
1327        } else {
1328            // This is the old way of removing all views in CameraRootView. Will
1329            // be deprecated soon. It is here to make sure modules that haven't
1330            // been refactored can still function.
1331            mCameraAppUI.clearCameraUI();
1332        }
1333
1334        openModule(mCurrentModule);
1335        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1336        // Store the module index so we can use it the next time the Camera
1337        // starts up.
1338        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1339        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1340    }
1341
1342    public void onSettingsSelected() {
1343        // Temporary until we finalize the touch flow.
1344        LayoutInflater inflater = getLayoutInflater();
1345        SettingsView settingsView = (SettingsView) inflater.inflate(R.layout.settings_list_layout,
1346            null, false);
1347        settingsView.setSettingsListener(mSettingsController);
1348        if (mFeedbackHelper != null) {
1349            settingsView.setFeedbackHelper(mFeedbackHelper);
1350        }
1351        PopupWindow popup = new PopupWindow(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
1352        popup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1353        popup.setOutsideTouchable(true);
1354        popup.setFocusable(true);
1355        popup.setContentView(settingsView);
1356        popup.showAtLocation(mModeListView.getRootView(), Gravity.CENTER, 0, 0);
1357    }
1358
1359    /**
1360     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1361     * index an sets it as mCurrentModule.
1362     */
1363    private void setModuleFromModeIndex(int modeIndex) {
1364        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1365        if (agent == null) {
1366            return;
1367        }
1368        if (!agent.requestAppForCamera()) {
1369            mCameraController.closeCamera();
1370        }
1371        mCurrentModeIndex = agent.getModuleId();
1372        mCurrentModule = (CameraModule)  agent.createModule(this);
1373    }
1374
1375    @Override
1376    public SettingsManager getSettingsManager() {
1377        return mSettingsManager;
1378    }
1379
1380    @Override
1381    public CameraServices getServices() {
1382        return (CameraServices) getApplication();
1383    }
1384
1385    @Override
1386    public SettingsController getSettingsController() {
1387        return mSettingsController;
1388    }
1389
1390    public ButtonManager getButtonManager() {
1391        if (mButtonManager == null) {
1392            mButtonManager = new ButtonManager(this);
1393        }
1394        return mButtonManager;
1395    }
1396
1397    /**
1398     * Creates an AlertDialog appropriate for choosing whether to enable location
1399     * on the first run of the app.
1400     */
1401    public AlertDialog getFirstTimeLocationAlert() {
1402        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1403        builder = SettingsView.getFirstTimeLocationAlertBuilder(builder, mSettingsController);
1404        if (builder != null) {
1405            return builder.create();
1406        } else {
1407            return null;
1408        }
1409    }
1410
1411    /**
1412     * Launches an ACTION_EDIT intent for the given local data item.
1413     */
1414    public void launchEditor(LocalData data) {
1415        Intent intent = new Intent(Intent.ACTION_EDIT)
1416                .setDataAndType(data.getContentUri(), data.getMimeType())
1417                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1418        try {
1419            launchActivityByIntent(intent);
1420        } catch (ActivityNotFoundException e) {
1421            launchActivityByIntent(Intent.createChooser(intent, null));
1422        }
1423    }
1424
1425    /**
1426     * Launch the tiny planet editor.
1427     *
1428     * @param data The data must be a 360 degree stereographically mapped
1429     *             panoramic image. It will not be modified, instead a new item
1430     *             with the result will be added to the filmstrip.
1431     */
1432    public void launchTinyPlanetEditor(LocalData data) {
1433        TinyPlanetFragment fragment = new TinyPlanetFragment();
1434        Bundle bundle = new Bundle();
1435        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1436        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1437        fragment.setArguments(bundle);
1438        fragment.show(getFragmentManager(), "tiny_planet");
1439    }
1440
1441    private void openModule(CameraModule module) {
1442        // Currently not all modules use the generic_module UI.
1443        // TODO: once all modules have a bottom bar, move this
1444        // logic into the app.
1445        if (module.isUsingBottomBar()) {
1446            int color = mModeListView.getModeThemeColor(mCurrentModeIndex);
1447            mCameraAppUI.setBottomBarColor(color);
1448        }
1449
1450        module.init(this, isSecureCamera(), isCaptureIntent());
1451        module.resume();
1452        module.onPreviewVisibilityChanged(!mFilmstripVisible);
1453    }
1454
1455    private void closeModule(CameraModule module) {
1456        module.pause();
1457    }
1458
1459    private void performDeletion() {
1460        if (!mPendingDeletion) {
1461            return;
1462        }
1463        hideUndoDeletionBar(false);
1464        mDataAdapter.executeDeletion(CameraActivity.this);
1465    }
1466
1467    public void showUndoDeletionBar() {
1468        if (mPendingDeletion) {
1469            performDeletion();
1470        }
1471        Log.v(TAG, "showing undo bar");
1472        mPendingDeletion = true;
1473        if (mUndoDeletionBar == null) {
1474            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1475                    mAboveFilmstripControlLayout, true);
1476            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1477            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1478            button.setOnClickListener(new View.OnClickListener() {
1479                @Override
1480                public void onClick(View view) {
1481                    mDataAdapter.undoDataRemoval();
1482                    hideUndoDeletionBar(true);
1483                }
1484            });
1485            // Setting undo bar clickable to avoid touch events going through
1486            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1487            mUndoDeletionBar.setClickable(true);
1488            // When there is user interaction going on with the undo button, we
1489            // do not want to hide the undo bar.
1490            button.setOnTouchListener(new View.OnTouchListener() {
1491                @Override
1492                public boolean onTouch(View v, MotionEvent event) {
1493                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1494                        mIsUndoingDeletion = true;
1495                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1496                        mIsUndoingDeletion = false;
1497                    }
1498                    return false;
1499                }
1500            });
1501        }
1502        mUndoDeletionBar.setAlpha(0f);
1503        mUndoDeletionBar.setVisibility(View.VISIBLE);
1504        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1505    }
1506
1507    private void hideUndoDeletionBar(boolean withAnimation) {
1508        Log.v(TAG, "Hiding undo deletion bar");
1509        mPendingDeletion = false;
1510        if (mUndoDeletionBar != null) {
1511            if (withAnimation) {
1512                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1513                        .setListener(new Animator.AnimatorListener() {
1514                            @Override
1515                            public void onAnimationStart(Animator animation) {
1516                                // Do nothing.
1517                            }
1518
1519                            @Override
1520                            public void onAnimationEnd(Animator animation) {
1521                                mUndoDeletionBar.setVisibility(View.GONE);
1522                            }
1523
1524                            @Override
1525                            public void onAnimationCancel(Animator animation) {
1526                                // Do nothing.
1527                            }
1528
1529                            @Override
1530                            public void onAnimationRepeat(Animator animation) {
1531                                // Do nothing.
1532                            }
1533                        }).start();
1534            } else {
1535                mUndoDeletionBar.setVisibility(View.GONE);
1536            }
1537        }
1538    }
1539
1540    @Override
1541    public void onOrientationChanged(int orientation) {
1542        // We keep the last known orientation. So if the user first orient
1543        // the camera then point the camera to floor or sky, we still have
1544        // the correct orientation.
1545        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1546            return;
1547        }
1548        mLastRawOrientation = orientation;
1549        if (mCurrentModule != null) {
1550            mCurrentModule.onOrientationChanged(orientation);
1551        }
1552    }
1553
1554    /**
1555     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1556     * capture intent.
1557     *
1558     * @param enable {@code true} to enable swipe.
1559     */
1560    public void setSwipingEnabled(boolean enable) {
1561        // TODO: Bring back the functionality.
1562        if (isCaptureIntent()) {
1563            //lockPreview(true);
1564        } else {
1565            //lockPreview(!enable);
1566        }
1567    }
1568
1569    // Accessor methods for getting latency times used in performance testing
1570    public long getAutoFocusTime() {
1571        return (mCurrentModule instanceof PhotoModule) ?
1572                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1573    }
1574
1575    public long getShutterLag() {
1576        return (mCurrentModule instanceof PhotoModule) ?
1577                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1578    }
1579
1580    public long getShutterToPictureDisplayedTime() {
1581        return (mCurrentModule instanceof PhotoModule) ?
1582                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1583    }
1584
1585    public long getPictureDisplayedToJpegCallbackTime() {
1586        return (mCurrentModule instanceof PhotoModule) ?
1587                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1588    }
1589
1590    public long getJpegCallbackFinishTime() {
1591        return (mCurrentModule instanceof PhotoModule) ?
1592                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1593    }
1594
1595    public long getCaptureStartTime() {
1596        return (mCurrentModule instanceof PhotoModule) ?
1597                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1598    }
1599
1600    public boolean isRecording() {
1601        return (mCurrentModule instanceof VideoModule) ?
1602                ((VideoModule) mCurrentModule).isRecording() : false;
1603    }
1604
1605    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1606        return mCameraController;
1607    }
1608
1609    // For debugging purposes only.
1610    public CameraModule getCurrentModule() {
1611        return mCurrentModule;
1612    }
1613
1614    private void keepScreenOnForAWhile() {
1615        if (mKeepScreenOn) {
1616            return;
1617        }
1618        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1619        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1620        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1621    }
1622
1623    private void resetScreenOn() {
1624        mKeepScreenOn = false;
1625        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1626        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1627    }
1628
1629    private void startGallery() {
1630        try {
1631            UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1632                    UsageStatistics.ACTION_GALLERY, null);
1633            launchActivityByIntent(IntentHelper.getGalleryIntent(CameraActivity.this));
1634        } catch (ActivityNotFoundException e) {
1635            Log.w(TAG, "Failed to launch gallery activity, closing");
1636        }
1637    }
1638
1639    private void setNfcBeamPushUriFromData(LocalData data) {
1640        final Uri uri = data.getContentUri();
1641        if (uri != Uri.EMPTY) {
1642            mNfcPushUris[0] = uri;
1643        } else {
1644            mNfcPushUris[0] = null;
1645        }
1646    }
1647
1648    /**
1649     * Updates the visibility of the filmstrip bottom controls.
1650     */
1651    private void updateUiByData(final int dataId) {
1652        if (isSecureCamera()) {
1653            // We cannot show buttons in secure camera since go to other
1654            // activities might create a security hole.
1655            return;
1656        }
1657
1658        final LocalData currentData = mDataAdapter.getLocalData(dataId);
1659        if (currentData == null) {
1660            Log.w(TAG, "Current data ID not found.");
1661            hideSessionProgress();
1662            return;
1663        }
1664
1665        setNfcBeamPushUriFromData(currentData);
1666
1667        /* Bottom controls. */
1668
1669        updateBottomControlsByData(currentData);
1670        if (!mDataAdapter.isMetadataUpdated(dataId)) {
1671            mDataAdapter.updateMetadata(this, dataId);
1672        }
1673    }
1674
1675    /**
1676     * Updates the bottom controls based on the data.
1677     */
1678    private void updateBottomControlsByData(final LocalData currentData) {
1679
1680        final CameraAppUI.BottomControls filmstripBottomControls =
1681                mCameraAppUI.getFilmstripBottomControls();
1682        filmstripBottomControls.setEditButtonVisibility(
1683                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
1684        filmstripBottomControls.setShareButtonVisibility(
1685                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
1686        filmstripBottomControls.setDeleteButtonVisibility(
1687                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
1688
1689        /* Progress bar */
1690
1691        Uri contentUri = currentData.getContentUri();
1692        CaptureSessionManager sessionManager = getServices()
1693                .getCaptureSessionManager();
1694        int sessionProgress = sessionManager.getSessionProgress(contentUri);
1695
1696        if (sessionProgress < 0) {
1697            hideSessionProgress();
1698        } else {
1699            CharSequence progressMessage = sessionManager
1700                    .getSessionProgressMessage(contentUri);
1701            showSessionProgress(progressMessage);
1702            updateSessionProgress(sessionProgress);
1703        }
1704
1705        /* View button */
1706
1707        // We need to add this to a separate DB.
1708        final int viewButtonVisibility;
1709        if (PanoramaMetadataLoader.isPanorama(currentData)) {
1710            viewButtonVisibility = CameraAppUI.BottomControls.VIEW_PHOTO_SPHERE;
1711        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
1712            viewButtonVisibility = CameraAppUI.BottomControls.VIEW_RGBZ;
1713        } else {
1714            viewButtonVisibility = CameraAppUI.BottomControls.VIEW_NONE;
1715        }
1716
1717        filmstripBottomControls.setTinyPlanetButtonVisibility(
1718                PanoramaMetadataLoader.isPanorama360(currentData));
1719        filmstripBottomControls.setViewButtonVisibility(viewButtonVisibility);
1720    }
1721}
1722