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