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