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