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