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