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