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