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