CameraActivity.java revision 26862c304a8163016b1f8196c715769750ba1720
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.content.ActivityNotFoundException;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.ServiceConnection;
31import android.content.SharedPreferences;
32import android.content.pm.ActivityInfo;
33import android.content.res.Configuration;
34import android.graphics.drawable.ColorDrawable;
35import android.net.Uri;
36import android.nfc.NfcAdapter;
37import android.nfc.NfcAdapter.CreateBeamUrisCallback;
38import android.nfc.NfcEvent;
39import android.os.AsyncTask;
40import android.os.Build;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.IBinder;
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.KeyEvent;
51import android.view.LayoutInflater;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55import android.view.MotionEvent;
56import android.view.OrientationEventListener;
57import android.view.View;
58import android.view.ViewGroup;
59import android.view.Window;
60import android.view.WindowManager;
61import android.widget.FrameLayout;
62import android.widget.ImageView;
63import android.widget.ProgressBar;
64import android.widget.ShareActionProvider;
65
66import com.android.camera.app.AppManagerFactory;
67import com.android.camera.app.PanoramaStitchingManager;
68import com.android.camera.crop.CropActivity;
69import com.android.camera.data.CameraDataAdapter;
70import com.android.camera.data.CameraPreviewData;
71import com.android.camera.data.FixedFirstDataAdapter;
72import com.android.camera.data.FixedLastDataAdapter;
73import com.android.camera.data.InProgressDataWrapper;
74import com.android.camera.data.LocalData;
75import com.android.camera.data.LocalDataAdapter;
76import com.android.camera.data.LocalMediaObserver;
77import com.android.camera.data.MediaDetails;
78import com.android.camera.data.SimpleViewData;
79import com.android.camera.tinyplanet.TinyPlanetFragment;
80import com.android.camera.ui.ModuleSwitcher;
81import com.android.camera.ui.DetailsDialog;
82import com.android.camera.ui.FilmStripView;
83import com.android.camera.util.ApiHelper;
84import com.android.camera.util.CameraUtil;
85import com.android.camera.util.GcamHelper;
86import com.android.camera.util.PhotoSphereHelper;
87import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
88import com.android.camera2.R;
89
90import static com.android.camera.CameraManager.CameraOpenErrorCallback;
91
92public class CameraActivity extends Activity
93        implements ModuleSwitcher.ModuleSwitchListener,
94        ActionBar.OnMenuVisibilityListener {
95
96    private static final String TAG = "CAM_Activity";
97
98    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
99            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
100    public static final String ACTION_IMAGE_CAPTURE_SECURE =
101            "android.media.action.IMAGE_CAPTURE_SECURE";
102    public static final String ACTION_TRIM_VIDEO =
103            "com.android.camera.action.TRIM";
104    public static final String MEDIA_ITEM_PATH = "media-item-path";
105
106    // The intent extra for camera from secure lock screen. True if the gallery
107    // should only show newly captured pictures. sSecureAlbumId does not
108    // increment. This is used when switching between camera, camcorder, and
109    // panorama. If the extra is not set, it is in the normal camera mode.
110    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
111
112    /**
113     * Request code from an activity we started that indicated that we do not
114     * want to reset the view to the preview in onResume.
115     */
116    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
117
118    private static final int HIDE_ACTION_BAR = 1;
119    private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000;
120
121    /** Whether onResume should reset the view to the preview. */
122    private boolean mResetToPreviewOnResume = true;
123
124    // Supported operations at FilmStripView. Different data has different
125    // set of supported operations.
126    private static final int SUPPORT_DELETE = 1 << 0;
127    private static final int SUPPORT_ROTATE = 1 << 1;
128    private static final int SUPPORT_INFO = 1 << 2;
129    private static final int SUPPORT_CROP = 1 << 3;
130    private static final int SUPPORT_SETAS = 1 << 4;
131    private static final int SUPPORT_EDIT = 1 << 5;
132    private static final int SUPPORT_TRIM = 1 << 6;
133    private static final int SUPPORT_SHARE = 1 << 7;
134    private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
135    private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
136    private static final int SUPPORT_ALL = 0xffffffff;
137
138    /** This data adapter is used by FilmStripView. */
139    private LocalDataAdapter mDataAdapter;
140    /** This data adapter represents the real local camera data. */
141    private LocalDataAdapter mWrappedDataAdapter;
142
143    private PanoramaStitchingManager mPanoramaManager;
144    private int mCurrentModuleIndex;
145    private CameraModule mCurrentModule;
146    private FrameLayout mAboveFilmstripControlLayout;
147    private View mCameraModuleRootView;
148    private FilmStripView mFilmStripView;
149    private ProgressBar mBottomProgress;
150    private View mPanoStitchingPanel;
151    private int mResultCodeForTesting;
152    private Intent mResultDataForTesting;
153    private OnScreenHint mStorageHint;
154    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
155    private boolean mAutoRotateScreen;
156    private boolean mSecureCamera;
157    // This is a hack to speed up the start of SecureCamera.
158    private static boolean sFirstStartAfterScreenOn = true;
159    private int mLastRawOrientation;
160    private MyOrientationEventListener mOrientationListener;
161    private Handler mMainHandler;
162    private PanoramaViewHelper mPanoramaViewHelper;
163    private CameraPreviewData mCameraPreviewData;
164    private ActionBar mActionBar;
165    private OnActionBarVisibilityListener mOnActionBarVisibilityListener = null;
166    private Menu mActionBarMenu;
167    private ViewGroup mUndoDeletionBar;
168    private boolean mIsUndoingDeletion = false;
169
170    private Uri[] mNfcPushUris = new Uri[1];
171
172    private ShareActionProvider mStandardShareActionProvider;
173    private Intent mStandardShareIntent;
174    private ShareActionProvider mPanoramaShareActionProvider;
175    private Intent mPanoramaShareIntent;
176    private LocalMediaObserver mLocalImagesObserver;
177    private LocalMediaObserver mLocalVideosObserver;
178
179    private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
180            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
181    private boolean mPendingDeletion = false;
182
183    private Intent mVideoShareIntent;
184    private Intent mImageShareIntent;
185
186    private class MyOrientationEventListener
187            extends OrientationEventListener {
188        public MyOrientationEventListener(Context context) {
189            super(context);
190        }
191
192        @Override
193        public void onOrientationChanged(int orientation) {
194            // We keep the last known orientation. So if the user first orient
195            // the camera then point the camera to floor or sky, we still have
196            // the correct orientation.
197            if (orientation == ORIENTATION_UNKNOWN) {
198                return;
199            }
200            mLastRawOrientation = orientation;
201            mCurrentModule.onOrientationChanged(orientation);
202        }
203    }
204
205    private MediaSaveService mMediaSaveService;
206    private ServiceConnection mConnection = new ServiceConnection() {
207        @Override
208        public void onServiceConnected(ComponentName className, IBinder b) {
209            mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
210            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
211        }
212
213        @Override
214        public void onServiceDisconnected(ComponentName className) {
215            if (mMediaSaveService != null) {
216                mMediaSaveService.setListener(null);
217                mMediaSaveService = null;
218            }
219        }
220    };
221
222    private CameraOpenErrorCallback mCameraOpenErrorCallback =
223            new CameraOpenErrorCallback() {
224                @Override
225                public void onCameraDisabled(int cameraId) {
226                    CameraUtil.showErrorAndFinish(CameraActivity.this,
227                            R.string.camera_disabled);
228                }
229
230                @Override
231                public void onDeviceOpenFailure(int cameraId) {
232                    CameraUtil.showErrorAndFinish(CameraActivity.this,
233                            R.string.cannot_connect_camera);
234                }
235
236                @Override
237                public void onReconnectionFailure(CameraManager mgr) {
238                    CameraUtil.showErrorAndFinish(CameraActivity.this,
239                            R.string.cannot_connect_camera);
240                }
241            };
242
243    // close activity when screen turns off
244    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
245        @Override
246        public void onReceive(Context context, Intent intent) {
247            finish();
248        }
249    };
250
251    private static BroadcastReceiver sScreenOffReceiver;
252
253    private static class ScreenOffReceiver extends BroadcastReceiver {
254        @Override
255        public void onReceive(Context context, Intent intent) {
256            sFirstStartAfterScreenOn = true;
257        }
258    }
259
260    private class MainHandler extends Handler {
261        public MainHandler(Looper looper) {
262            super(looper);
263        }
264
265        @Override
266        public void handleMessage(Message msg) {
267            if (msg.what == HIDE_ACTION_BAR) {
268                removeMessages(HIDE_ACTION_BAR);
269                CameraActivity.this.setSystemBarsVisibility(false);
270            }
271        }
272    }
273
274    public interface OnActionBarVisibilityListener {
275        public void onActionBarVisibilityChanged(boolean isVisible);
276    }
277
278    public void setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener) {
279        mOnActionBarVisibilityListener = listener;
280    }
281
282    public static boolean isFirstStartAfterScreenOn() {
283        return sFirstStartAfterScreenOn;
284    }
285
286    public static void resetFirstStartAfterScreenOn() {
287        sFirstStartAfterScreenOn = false;
288    }
289
290    private FilmStripView.Listener mFilmStripListener =
291            new FilmStripView.Listener() {
292                @Override
293                public void onDataPromoted(int dataID) {
294                    removeData(dataID);
295                }
296
297                @Override
298                public void onDataDemoted(int dataID) {
299                    removeData(dataID);
300                }
301
302                @Override
303                public void onDataFullScreenChange(int dataID, boolean full) {
304                    boolean isCameraID = isCameraPreview(dataID);
305                    if (!isCameraID) {
306                        if (!full) {
307                            // Always show action bar in filmstrip mode
308                            CameraActivity.this.setSystemBarsVisibility(true, false);
309                        } else if (mActionBar.isShowing()) {
310                            // Hide action bar after time out in full screen mode
311                            mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR,
312                                    SHOW_ACTION_BAR_TIMEOUT_MS);
313                        }
314                    }
315                }
316
317                /**
318                 * Check if the local data corresponding to dataID is the camera
319                 * preview.
320                 *
321                 * @param dataID the ID of the local data
322                 * @return true if the local data is not null and it is the
323                 *         camera preview.
324                 */
325                private boolean isCameraPreview(int dataID) {
326                    LocalData localData = mDataAdapter.getLocalData(dataID);
327                    if (localData == null) {
328                        Log.w(TAG, "Current data ID not found.");
329                        return false;
330                    }
331                    return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
332                }
333
334                @Override
335                public void onCurrentDataChanged(final int dataID, final boolean current) {
336                    // Delay hiding action bar if there is any user interaction
337                    if (mMainHandler.hasMessages(HIDE_ACTION_BAR)) {
338                        mMainHandler.removeMessages(HIDE_ACTION_BAR);
339                        mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR,
340                                SHOW_ACTION_BAR_TIMEOUT_MS);
341                    }
342                    runOnUiThread(new Runnable() {
343                        @Override
344                        public void run() {
345                            LocalData currentData = mDataAdapter.getLocalData(dataID);
346                            if (currentData == null) {
347                                Log.w(TAG, "Current data ID not found.");
348                                hidePanoStitchingProgress();
349                                return;
350                            }
351                            boolean isCameraID = currentData.getLocalDataType() ==
352                                    LocalData.LOCAL_CAMERA_PREVIEW;
353                            if (!current) {
354                                if (isCameraID) {
355                                    mCurrentModule.onPreviewFocusChanged(false);
356                                    CameraActivity.this.setSystemBarsVisibility(true);
357                                }
358                                hidePanoStitchingProgress();
359                            } else {
360                                if (isCameraID) {
361                                    mCurrentModule.onPreviewFocusChanged(true);
362                                    // Don't show the action bar in Camera
363                                    // preview.
364                                    CameraActivity.this.setSystemBarsVisibility(false);
365                                    if (mPendingDeletion) {
366                                        performDeletion();
367                                    }
368                                } else {
369                                    updateActionBarMenu(dataID);
370                                }
371
372                                Uri contentUri = currentData.getContentUri();
373                                if (contentUri == null) {
374                                    hidePanoStitchingProgress();
375                                    return;
376                                }
377                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(
378                                        contentUri);
379                                if (panoStitchingProgress < 0) {
380                                    hidePanoStitchingProgress();
381                                    return;
382                                }
383                                showPanoStitchingProgress();
384                                updateStitchingProgress(panoStitchingProgress);
385                            }
386                        }
387                    });
388                }
389
390                @Override
391                public void onToggleSystemDecorsVisibility(int dataID) {
392                    // If action bar is showing, hide it immediately, otherwise
393                    // show action bar and hide it later
394                    if (mActionBar.isShowing()) {
395                        CameraActivity.this.setSystemBarsVisibility(false);
396                    } else {
397                        // Don't show the action bar if that is the camera preview.
398                        boolean isCameraID = isCameraPreview(dataID);
399                        if (!isCameraID) {
400                            CameraActivity.this.setSystemBarsVisibility(true, true);
401                        }
402                    }
403                }
404
405                @Override
406                public void setSystemDecorsVisibility(boolean visible) {
407                    CameraActivity.this.setSystemBarsVisibility(visible);
408                }
409            };
410
411    public void gotoGallery() {
412        mFilmStripView.getController().goToNextItem();
413    }
414
415    /**
416     * If {@param visible} is false, this hides the action bar and switches the system UI
417     * to lights-out mode.
418     */
419
420    private void setSystemBarsVisibility(boolean visible) {
421        setSystemBarsVisibility(visible, false);
422    }
423
424    /**
425     * If {@param visible} is false, this hides the action bar and switches the
426     * system UI to lights-out mode. If {@param hideLater} is true, a delayed message
427     * will be sent after a timeout to hide the action bar.
428     */
429    private void setSystemBarsVisibility(boolean visible, boolean hideLater) {
430        mMainHandler.removeMessages(HIDE_ACTION_BAR);
431        boolean currentlyVisible = mActionBar.isShowing();
432
433        if (visible != currentlyVisible) {
434            int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (visible ? View.SYSTEM_UI_FLAG_VISIBLE
435                    : View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
436            mAboveFilmstripControlLayout.setSystemUiVisibility(visibility);
437            if (visible) {
438                mActionBar.show();
439            } else {
440                mActionBar.hide();
441            }
442            if (mOnActionBarVisibilityListener != null) {
443                mOnActionBarVisibilityListener.onActionBarVisibilityChanged(visible);
444            }
445        }
446
447        // Now delay hiding the bars
448        if (visible && hideLater) {
449            mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
450        }
451    }
452
453    private void hidePanoStitchingProgress() {
454        mPanoStitchingPanel.setVisibility(View.GONE);
455    }
456
457    private void showPanoStitchingProgress() {
458        mPanoStitchingPanel.setVisibility(View.VISIBLE);
459    }
460
461    private void updateStitchingProgress(int progress) {
462        mBottomProgress.setProgress(progress);
463    }
464
465    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
466    private void setupNfcBeamPush() {
467        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this);
468        if (adapter == null) {
469            return;
470        }
471
472        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
473            // Disable beaming
474            adapter.setNdefPushMessage(null, CameraActivity.this);
475            return;
476        }
477
478        adapter.setBeamPushUris(null, CameraActivity.this);
479        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
480            @Override
481            public Uri[] createBeamUris(NfcEvent event) {
482                return mNfcPushUris;
483            }
484        }, CameraActivity.this);
485    }
486
487    private void setNfcBeamPushUri(Uri uri) {
488        mNfcPushUris[0] = uri;
489    }
490
491    private void setStandardShareIntent(Uri contentUri, String mimeType) {
492        mStandardShareIntent = getShareIntentFromType(mimeType);
493        if (mStandardShareIntent != null) {
494            mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
495            mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
496            if (mStandardShareActionProvider != null) {
497                mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
498            }
499        }
500    }
501
502    /**
503     * Get the share intent according to the mimeType
504     *
505     * @param mimeType The mimeType of current data.
506     * @return the video/image's ShareIntent or null if mimeType is invalid.
507     */
508    private Intent getShareIntentFromType(String mimeType) {
509        // Lazily create the intent object.
510        if (mimeType.startsWith("video/")) {
511            if (mVideoShareIntent == null) {
512                mVideoShareIntent = new Intent(Intent.ACTION_SEND);
513                mVideoShareIntent.setType("video/*");
514            }
515            return mVideoShareIntent;
516        } else if (mimeType.startsWith("image/")) {
517            if (mImageShareIntent == null) {
518                mImageShareIntent = new Intent(Intent.ACTION_SEND);
519                mImageShareIntent.setType("image/*");
520            }
521            return mImageShareIntent;
522        }
523        Log.w(TAG, "unsupported mimeType " + mimeType);
524        return null;
525    }
526
527    private void setPanoramaShareIntent(Uri contentUri) {
528        if (mPanoramaShareIntent == null) {
529            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
530        }
531        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
532        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
533        if (mPanoramaShareActionProvider != null) {
534            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
535        }
536    }
537
538    @Override
539    public void onMenuVisibilityChanged(boolean isVisible) {
540        // If menu is showing, we need to make sure action bar does not go away.
541        mMainHandler.removeMessages(HIDE_ACTION_BAR);
542        if (!isVisible) {
543            mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
544        }
545    }
546
547    /**
548     * According to the data type, make the menu items for supported operations
549     * visible.
550     *
551     * @param dataID the data ID of the current item.
552     */
553    private void updateActionBarMenu(int dataID) {
554        LocalData currentData = mDataAdapter.getLocalData(dataID);
555        if (currentData == null) {
556            return;
557        }
558        int type = currentData.getLocalDataType();
559
560        if (mActionBarMenu == null) {
561            return;
562        }
563
564        int supported = 0;
565
566        switch (type) {
567            case LocalData.LOCAL_IMAGE:
568                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
569                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
570                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
571                break;
572            case LocalData.LOCAL_VIDEO:
573                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
574                        | SUPPORT_SHARE;
575                break;
576            case LocalData.LOCAL_PHOTO_SPHERE:
577                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
578                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
579                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
580                break;
581            case LocalData.LOCAL_360_PHOTO_SPHERE:
582                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
583                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
584                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
585                        | SUPPORT_SHOW_ON_MAP;
586                break;
587            default:
588                break;
589        }
590
591        // In secure camera mode, we only support delete operation.
592        if (isSecureCamera()) {
593            supported &= SUPPORT_DELETE;
594        }
595
596        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
597                (supported & SUPPORT_DELETE) != 0);
598        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
599                (supported & SUPPORT_ROTATE) != 0);
600        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
601                (supported & SUPPORT_ROTATE) != 0);
602        setMenuItemVisible(mActionBarMenu, R.id.action_details,
603                (supported & SUPPORT_INFO) != 0);
604        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
605                (supported & SUPPORT_CROP) != 0);
606        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
607                (supported & SUPPORT_SETAS) != 0);
608        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
609                (supported & SUPPORT_EDIT) != 0);
610        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
611                (supported & SUPPORT_TRIM) != 0);
612
613        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
614        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
615        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
616        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
617
618        if (panoramaShare) {
619            // For 360 PhotoSphere, relegate standard share to the overflow menu
620            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
621            if (item != null) {
622                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
623                item.setTitle(getResources().getString(R.string.share_as_photo));
624            }
625            // And, promote "share as panorama" to action bar
626            item = mActionBarMenu.findItem(R.id.action_share_panorama);
627            if (item != null) {
628                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
629            }
630            setPanoramaShareIntent(currentData.getContentUri());
631        }
632        if (standardShare) {
633            if (!panoramaShare) {
634                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
635                if (item != null) {
636                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
637                    item.setTitle(getResources().getString(R.string.share));
638                }
639            }
640            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
641            setNfcBeamPushUri(currentData.getContentUri());
642        }
643
644        boolean itemHasLocation = currentData.getLatLong() != null;
645        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
646                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
647    }
648
649    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
650        MenuItem item = menu.findItem(itemId);
651        if (item != null)
652            item.setVisible(visible);
653    }
654
655    private ImageTaskManager.TaskListener mStitchingListener =
656            new ImageTaskManager.TaskListener() {
657                @Override
658                public void onTaskQueued(String filePath, final Uri imageUri) {
659                    mMainHandler.post(new Runnable() {
660                        @Override
661                        public void run() {
662                            notifyNewMedia(imageUri);
663                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
664                            if (dataID != -1) {
665                                // Don't allow special UI actions (swipe to
666                                // delete, for example) on in-progress data.
667                                LocalData d = mDataAdapter.getLocalData(dataID);
668                                InProgressDataWrapper newData = new InProgressDataWrapper(d);
669                                mDataAdapter.updateData(dataID, newData);
670                            }
671                        }
672                    });
673                }
674
675                @Override
676                public void onTaskDone(String filePath, final Uri imageUri) {
677                    Log.v(TAG, "onTaskDone:" + filePath);
678                    mMainHandler.post(new Runnable() {
679                        @Override
680                        public void run() {
681                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
682                            int currentDataId = mFilmStripView.getCurrentId();
683
684                            if (currentDataId == doneID) {
685                                hidePanoStitchingProgress();
686                                updateStitchingProgress(0);
687                            }
688
689                            mDataAdapter.refresh(getContentResolver(), imageUri);
690                        }
691                    });
692                }
693
694                @Override
695                public void onTaskProgress(
696                        String filePath, final Uri imageUri, final int progress) {
697                    mMainHandler.post(new Runnable() {
698                        @Override
699                        public void run() {
700                            int currentDataId = mFilmStripView.getCurrentId();
701                            if (currentDataId == -1) {
702                                return;
703                            }
704                            if (imageUri.equals(
705                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
706                                updateStitchingProgress(progress);
707                            }
708                        }
709                    });
710                }
711            };
712
713    public MediaSaveService getMediaSaveService() {
714        return mMediaSaveService;
715    }
716
717    public void notifyNewMedia(Uri uri) {
718        ContentResolver cr = getContentResolver();
719        String mimeType = cr.getType(uri);
720        if (mimeType.startsWith("video/")) {
721            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
722            mDataAdapter.addNewVideo(cr, uri);
723        } else if (mimeType.startsWith("image/")) {
724            CameraUtil.broadcastNewPicture(this, uri);
725            mDataAdapter.addNewPhoto(cr, uri);
726        } else if (mimeType.startsWith("application/stitching-preview")) {
727            mDataAdapter.addNewPhoto(cr, uri);
728        } else {
729            android.util.Log.w(TAG, "Unknown new media with MIME type:"
730                    + mimeType + ", uri:" + uri);
731        }
732    }
733
734    private void removeData(int dataID) {
735        mDataAdapter.removeData(CameraActivity.this, dataID);
736        if (mDataAdapter.getTotalNumber() > 1) {
737            showUndoDeletionBar();
738        } else {
739            // If camera preview is the only view left in filmstrip,
740            // no need to show undo bar.
741            mPendingDeletion = true;
742            performDeletion();
743        }
744    }
745
746    private void bindMediaSaveService() {
747        Intent intent = new Intent(this, MediaSaveService.class);
748        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
749    }
750
751    private void unbindMediaSaveService() {
752        if (mConnection != null) {
753            unbindService(mConnection);
754        }
755    }
756
757    @Override
758    public boolean onCreateOptionsMenu(Menu menu) {
759        // Inflate the menu items for use in the action bar
760        MenuInflater inflater = getMenuInflater();
761        inflater.inflate(R.menu.operations, menu);
762        mActionBarMenu = menu;
763
764        // Configure the standard share action provider
765        MenuItem item = menu.findItem(R.id.action_share);
766        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
767        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
768        if (mStandardShareIntent != null) {
769            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
770        }
771
772        // Configure the panorama share action provider
773        item = menu.findItem(R.id.action_share_panorama);
774        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
775        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
776        if (mPanoramaShareIntent != null) {
777            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
778        }
779
780        return super.onCreateOptionsMenu(menu);
781    }
782
783    @Override
784    public boolean onOptionsItemSelected(MenuItem item) {
785        int currentDataId = mFilmStripView.getCurrentId();
786        if (currentDataId < 0) {
787            return false;
788        }
789        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
790
791        // Handle presses on the action bar items
792        switch (item.getItemId()) {
793            case android.R.id.home:
794                // ActionBar's Up/Home button was clicked
795                try {
796                    if (!CameraUtil.launchGallery(CameraActivity.this)) {
797                        mFilmStripView.getController().goToFirstItem();
798                    }
799                    return true;
800                } catch (ActivityNotFoundException e) {
801                    Log.w(TAG, "No activity found to handle APP_GALLERY category!");
802                    finish();
803                }
804            case R.id.action_delete:
805                removeData(currentDataId);
806                return true;
807            case R.id.action_edit:
808                launchEditor(localData);
809                return true;
810            case R.id.action_trim: {
811                // This is going to be handled by the Gallery app.
812                Intent intent = new Intent(ACTION_TRIM_VIDEO);
813                LocalData currentData = mDataAdapter.getLocalData(
814                        mFilmStripView.getCurrentId());
815                intent.setData(currentData.getContentUri());
816                // We need the file path to wrap this into a RandomAccessFile.
817                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
818                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
819                return true;
820            }
821            case R.id.action_rotate_ccw:
822                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
823                return true;
824            case R.id.action_rotate_cw:
825                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
826                return true;
827            case R.id.action_crop: {
828                Intent intent = new Intent(CropActivity.CROP_ACTION);
829                intent.setClass(this, CropActivity.class);
830                intent.setDataAndType(localData.getContentUri(), localData.getMimeType())
831                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
832                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
833                return true;
834            }
835            case R.id.action_setas: {
836                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
837                        .setDataAndType(localData.getContentUri(),
838                                localData.getMimeType())
839                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
840                intent.putExtra("mimeType", intent.getType());
841                startActivityForResult(Intent.createChooser(
842                        intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
843                return true;
844            }
845            case R.id.action_details:
846                (new AsyncTask<Void, Void, MediaDetails>() {
847                    @Override
848                    protected MediaDetails doInBackground(Void... params) {
849                        return localData.getMediaDetails(CameraActivity.this);
850                    }
851
852                    @Override
853                    protected void onPostExecute(MediaDetails mediaDetails) {
854                        DetailsDialog.create(CameraActivity.this, mediaDetails).show();
855                    }
856                }).execute();
857                return true;
858            case R.id.action_show_on_map:
859                double[] latLong = localData.getLatLong();
860                if (latLong != null) {
861                    CameraUtil.showOnMap(this, latLong);
862                }
863                return true;
864            default:
865                return super.onOptionsItemSelected(item);
866        }
867    }
868
869    private boolean isCaptureIntent() {
870        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
871                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
872                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
873            return true;
874        } else {
875            return false;
876        }
877    }
878
879    @Override
880    public void onCreate(Bundle state) {
881        super.onCreate(state);
882        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
883        setContentView(R.layout.camera_filmstrip);
884        mActionBar = getActionBar();
885        mActionBar.addOnMenuVisibilityListener(this);
886
887        if (ApiHelper.HAS_ROTATION_ANIMATION) {
888            setRotationAnimation();
889        }
890
891        mMainHandler = new MainHandler(getMainLooper());
892        // Check if this is in the secure camera mode.
893        Intent intent = getIntent();
894        String action = intent.getAction();
895        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
896                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
897            mSecureCamera = true;
898        } else {
899            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
900        }
901
902        if (mSecureCamera) {
903            // Change the window flags so that secure camera can show when locked
904            Window win = getWindow();
905            WindowManager.LayoutParams params = win.getAttributes();
906            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
907            win.setAttributes(params);
908
909            // Filter for screen off so that we can finish secure camera activity
910            // when screen is off.
911            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
912            registerReceiver(mScreenOffReceiver, filter);
913            // TODO: This static screen off event receiver is a workaround to the
914            // double onResume() invocation (onResume->onPause->onResume). We should
915            // find a better solution to this.
916            if (sScreenOffReceiver == null) {
917                sScreenOffReceiver = new ScreenOffReceiver();
918                registerReceiver(sScreenOffReceiver, filter);
919            }
920        }
921        mAboveFilmstripControlLayout =
922                (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
923        mAboveFilmstripControlLayout.setFitsSystemWindows(true);
924        // Hide action bar first since we are in full screen mode first, and
925        // switch the system UI to lights-out mode.
926        this.setSystemBarsVisibility(false);
927        mPanoramaManager = AppManagerFactory.getInstance(this)
928                .getPanoramaStitchingManager();
929        mPanoramaManager.addTaskListener(mStitchingListener);
930        LayoutInflater inflater = getLayoutInflater();
931        View rootLayout = inflater.inflate(R.layout.camera, null, false);
932        mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root);
933        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
934        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
935        mCameraPreviewData = new CameraPreviewData(rootLayout,
936                FilmStripView.ImageData.SIZE_FULL,
937                FilmStripView.ImageData.SIZE_FULL);
938        // Put a CameraPreviewData at the first position.
939        mWrappedDataAdapter = new FixedFirstDataAdapter(
940                new CameraDataAdapter(new ColorDrawable(
941                        getResources().getColor(R.color.photo_placeholder))),
942                mCameraPreviewData);
943        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
944        mFilmStripView.setViewGap(
945                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
946        mPanoramaViewHelper = new PanoramaViewHelper(this);
947        mPanoramaViewHelper.onCreate();
948        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
949        // Set up the camera preview first so the preview shows up ASAP.
950        mFilmStripView.setListener(mFilmStripListener);
951
952        int moduleIndex = -1;
953        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
954                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
955            moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX;
956        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
957                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
958                        .getAction())
959                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
960                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
961            moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
962        } else {
963            // If the activity has not been started using an explicit intent,
964            // read the module index from the last time the user changed modes
965            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
966            moduleIndex = prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1);
967            if ((moduleIndex == ModuleSwitcher.GCAM_MODULE_INDEX &&
968                    !GcamHelper.hasGcamCapture()) || moduleIndex < 0) {
969                moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
970            }
971        }
972
973        mOrientationListener = new MyOrientationEventListener(this);
974        setModuleFromIndex(moduleIndex);
975        mCurrentModule.init(this, mCameraModuleRootView);
976
977        if (!mSecureCamera) {
978            mDataAdapter = mWrappedDataAdapter;
979            mFilmStripView.setDataAdapter(mDataAdapter);
980            if (!isCaptureIntent()) {
981                mDataAdapter.requestLoad(getContentResolver());
982            }
983        } else {
984            // Put a lock placeholder as the last image by setting its date to
985            // 0.
986            ImageView v = (ImageView) getLayoutInflater().inflate(
987                    R.layout.secure_album_placeholder, null);
988            v.setOnClickListener(new View.OnClickListener() {
989                @Override
990                public void onClick(View view) {
991                    CameraUtil.launchGallery(CameraActivity.this);
992                    finish();
993                }
994            });
995            mDataAdapter = new FixedLastDataAdapter(
996                    mWrappedDataAdapter,
997                    new SimpleViewData(
998                            v,
999                            v.getDrawable().getIntrinsicWidth(),
1000                            v.getDrawable().getIntrinsicHeight(),
1001                            0, 0));
1002            // Flush out all the original data.
1003            mDataAdapter.flush();
1004            mFilmStripView.setDataAdapter(mDataAdapter);
1005        }
1006
1007        setupNfcBeamPush();
1008
1009        mLocalImagesObserver = new LocalMediaObserver();
1010        mLocalVideosObserver = new LocalMediaObserver();
1011
1012        getContentResolver().registerContentObserver(
1013                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1014                mLocalImagesObserver);
1015        getContentResolver().registerContentObserver(
1016                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1017                mLocalVideosObserver);
1018    }
1019
1020    private void setRotationAnimation() {
1021        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1022        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1023        Window win = getWindow();
1024        WindowManager.LayoutParams winParams = win.getAttributes();
1025        winParams.rotationAnimation = rotationAnimation;
1026        win.setAttributes(winParams);
1027    }
1028
1029    @Override
1030    public void onUserInteraction() {
1031        super.onUserInteraction();
1032        mCurrentModule.onUserInteraction();
1033    }
1034
1035    @Override
1036    public boolean dispatchTouchEvent(MotionEvent ev) {
1037        boolean result = super.dispatchTouchEvent(ev);
1038        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1039            // Real deletion is postponed until the next user interaction after
1040            // the gesture that triggers deletion. Until real deletion is performed,
1041            // users can click the undo button to bring back the image that they
1042            // chose to delete.
1043            if (mPendingDeletion && !mIsUndoingDeletion) {
1044                 performDeletion();
1045            }
1046        }
1047        return result;
1048    }
1049
1050    @Override
1051    public void onPause() {
1052        // Delete photos that are pending deletion
1053        performDeletion();
1054        mOrientationListener.disable();
1055        mCurrentModule.onPauseBeforeSuper();
1056        super.onPause();
1057        mCurrentModule.onPauseAfterSuper();
1058
1059        mLocalImagesObserver.setActivityPaused(true);
1060        mLocalVideosObserver.setActivityPaused(true);
1061    }
1062
1063    @Override
1064    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1065        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1066            mResetToPreviewOnResume = false;
1067        } else {
1068            super.onActivityResult(requestCode, resultCode, data);
1069        }
1070    }
1071
1072    @Override
1073    public void onResume() {
1074        // TODO: Handle this in OrientationManager.
1075        // Auto-rotate off
1076        if (Settings.System.getInt(getContentResolver(),
1077                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1078            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1079            mAutoRotateScreen = false;
1080        } else {
1081            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1082            mAutoRotateScreen = true;
1083        }
1084        mOrientationListener.enable();
1085        mCurrentModule.onResumeBeforeSuper();
1086        super.onResume();
1087        mCurrentModule.onResumeAfterSuper();
1088
1089        setSwipingEnabled(true);
1090
1091        if (mResetToPreviewOnResume) {
1092            // Go to the preview on resume.
1093            mFilmStripView.getController().goToFirstItem();
1094        }
1095        // Default is showing the preview, unless disabled by explicitly
1096        // starting an activity we want to return from to the filmstrip rather
1097        // than the preview.
1098        mResetToPreviewOnResume = true;
1099
1100        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1101                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1102            mDataAdapter.requestLoad(getContentResolver());
1103        }
1104        mLocalImagesObserver.setActivityPaused(false);
1105        mLocalVideosObserver.setActivityPaused(false);
1106    }
1107
1108    @Override
1109    public void onStart() {
1110        super.onStart();
1111        bindMediaSaveService();
1112        mPanoramaViewHelper.onStart();
1113    }
1114
1115    @Override
1116    protected void onStop() {
1117        super.onStop();
1118        mPanoramaViewHelper.onStop();
1119        unbindMediaSaveService();
1120    }
1121
1122    @Override
1123    public void onDestroy() {
1124        if (mSecureCamera) {
1125            unregisterReceiver(mScreenOffReceiver);
1126        }
1127        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1128        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1129
1130        super.onDestroy();
1131    }
1132
1133    @Override
1134    public void onConfigurationChanged(Configuration config) {
1135        super.onConfigurationChanged(config);
1136        mCurrentModule.onConfigurationChanged(config);
1137    }
1138
1139    @Override
1140    public boolean onKeyDown(int keyCode, KeyEvent event) {
1141        if (mFilmStripView.inCameraFullscreen()) {
1142            if (mCurrentModule.onKeyDown(keyCode, event)) {
1143                return true;
1144            }
1145            // Prevent software keyboard or voice search from showing up.
1146            if (keyCode == KeyEvent.KEYCODE_SEARCH
1147                    || keyCode == KeyEvent.KEYCODE_MENU) {
1148                if (event.isLongPress()) {
1149                    return true;
1150                }
1151            }
1152        }
1153
1154        return super.onKeyDown(keyCode, event);
1155    }
1156
1157    @Override
1158    public boolean onKeyUp(int keyCode, KeyEvent event) {
1159        if (mFilmStripView.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
1160            return true;
1161        }
1162        return super.onKeyUp(keyCode, event);
1163    }
1164
1165    @Override
1166    public void onBackPressed() {
1167        if (!mFilmStripView.inCameraFullscreen()) {
1168            mFilmStripView.getController().goToFirstItem();
1169        } else if (!mCurrentModule.onBackPressed()) {
1170            super.onBackPressed();
1171        }
1172    }
1173
1174    public boolean isAutoRotateScreen() {
1175        return mAutoRotateScreen;
1176    }
1177
1178    protected void updateStorageSpace() {
1179        mStorageSpaceBytes = Storage.getAvailableSpace();
1180    }
1181
1182    protected long getStorageSpaceBytes() {
1183        return mStorageSpaceBytes;
1184    }
1185
1186    protected void updateStorageSpaceAndHint() {
1187        updateStorageSpace();
1188        updateStorageHint(mStorageSpaceBytes);
1189    }
1190
1191    protected void updateStorageHint(long storageSpace) {
1192        String message = null;
1193        if (storageSpace == Storage.UNAVAILABLE) {
1194            message = getString(R.string.no_storage);
1195        } else if (storageSpace == Storage.PREPARING) {
1196            message = getString(R.string.preparing_sd);
1197        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1198            message = getString(R.string.access_sd_fail);
1199        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1200            message = getString(R.string.spaceIsLow_content);
1201        }
1202
1203        if (message != null) {
1204            if (mStorageHint == null) {
1205                mStorageHint = OnScreenHint.makeText(this, message);
1206            } else {
1207                mStorageHint.setText(message);
1208            }
1209            mStorageHint.show();
1210        } else if (mStorageHint != null) {
1211            mStorageHint.cancel();
1212            mStorageHint = null;
1213        }
1214    }
1215
1216    protected void setResultEx(int resultCode) {
1217        mResultCodeForTesting = resultCode;
1218        setResult(resultCode);
1219    }
1220
1221    protected void setResultEx(int resultCode, Intent data) {
1222        mResultCodeForTesting = resultCode;
1223        mResultDataForTesting = data;
1224        setResult(resultCode, data);
1225    }
1226
1227    public int getResultCode() {
1228        return mResultCodeForTesting;
1229    }
1230
1231    public Intent getResultData() {
1232        return mResultDataForTesting;
1233    }
1234
1235    public boolean isSecureCamera() {
1236        return mSecureCamera;
1237    }
1238
1239    @Override
1240    public void onModuleSelected(int moduleIndex) {
1241        if (mCurrentModuleIndex == moduleIndex) {
1242            return;
1243        }
1244
1245        CameraHolder.instance().keep();
1246        closeModule(mCurrentModule);
1247        setModuleFromIndex(moduleIndex);
1248
1249        openModule(mCurrentModule);
1250        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1251        if (mMediaSaveService != null) {
1252            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
1253        }
1254
1255        // Store the module index so we can use it the next time the Camera
1256        // starts up.
1257        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1258        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, moduleIndex).apply();
1259    }
1260
1261    /**
1262     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1263     * index an sets it as mCurrentModule.
1264     */
1265    private void setModuleFromIndex(int moduleIndex) {
1266        mCurrentModuleIndex = moduleIndex;
1267        switch (moduleIndex) {
1268            case ModuleSwitcher.VIDEO_MODULE_INDEX:
1269                mCurrentModule = new VideoModule();
1270                break;
1271
1272            case ModuleSwitcher.PHOTO_MODULE_INDEX:
1273                mCurrentModule = new PhotoModule();
1274                break;
1275
1276            case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX:
1277                mCurrentModule = new WideAnglePanoramaModule();
1278                break;
1279
1280            case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX:
1281                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
1282                break;
1283            case ModuleSwitcher.GCAM_MODULE_INDEX:
1284                // Force immediate release of Camera instance
1285                CameraHolder.instance().strongRelease();
1286                mCurrentModule = GcamHelper.createGcamModule();
1287                break;
1288            default:
1289                // Fall back to photo mode.
1290                mCurrentModule = new PhotoModule();
1291                mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
1292                break;
1293        }
1294    }
1295
1296    /**
1297     * Launches an ACTION_EDIT intent for the given local data item.
1298     */
1299    public void launchEditor(LocalData data) {
1300        Intent intent = new Intent(Intent.ACTION_EDIT)
1301                .setDataAndType(data.getContentUri(), data.getMimeType())
1302                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1303        startActivityForResult(Intent.createChooser(intent, null),
1304                REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1305    }
1306
1307    /**
1308     * Launch the tiny planet editor.
1309     *
1310     * @param data the data must be a 360 degree stereographically mapped
1311     *            panoramic image. It will not be modified, instead a new item
1312     *            with the result will be added to the filmstrip.
1313     */
1314    public void launchTinyPlanetEditor(LocalData data) {
1315        TinyPlanetFragment fragment = new TinyPlanetFragment();
1316        Bundle bundle = new Bundle();
1317        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1318        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1319        fragment.setArguments(bundle);
1320        fragment.show(getFragmentManager(), "tiny_planet");
1321    }
1322
1323    private void openModule(CameraModule module) {
1324        module.init(this, mCameraModuleRootView);
1325        module.onResumeBeforeSuper();
1326        module.onResumeAfterSuper();
1327    }
1328
1329    private void closeModule(CameraModule module) {
1330        module.onPauseBeforeSuper();
1331        module.onPauseAfterSuper();
1332        ((ViewGroup) mCameraModuleRootView).removeAllViews();
1333    }
1334
1335    private void performDeletion() {
1336        if (!mPendingDeletion) {
1337            return;
1338        }
1339        hideUndoDeletionBar(false);
1340        mDataAdapter.executeDeletion(CameraActivity.this);
1341        updateActionBarMenu(mFilmStripView.getCurrentId());
1342    }
1343
1344    public void showUndoDeletionBar() {
1345        if (mPendingDeletion) {
1346            performDeletion();
1347        }
1348        Log.v(TAG, "showing undo bar");
1349        mPendingDeletion = true;
1350        if (mUndoDeletionBar == null) {
1351            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
1352                    R.layout.undo_bar, mAboveFilmstripControlLayout, true);
1353            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1354            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1355            button.setOnClickListener(new View.OnClickListener() {
1356                @Override
1357                public void onClick(View view) {
1358                    mDataAdapter.undoDataRemoval();
1359                    hideUndoDeletionBar(true);
1360                }
1361            });
1362            // Setting undo bar clickable to avoid touch events going through
1363            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1364            mUndoDeletionBar.setClickable(true);
1365            // When there is user interaction going on with the undo button, we
1366            // do not want to hide the undo bar.
1367            button.setOnTouchListener(new View.OnTouchListener() {
1368                @Override
1369                public boolean onTouch(View v, MotionEvent event) {
1370                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1371                        mIsUndoingDeletion = true;
1372                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1373                        mIsUndoingDeletion =false;
1374                    }
1375                    return false;
1376                }
1377            });
1378        }
1379        mUndoDeletionBar.setAlpha(0f);
1380        mUndoDeletionBar.setVisibility(View.VISIBLE);
1381        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1382    }
1383
1384    private void hideUndoDeletionBar(boolean withAnimation) {
1385        Log.v(TAG, "Hiding undo deletion bar");
1386        mPendingDeletion = false;
1387        if (mUndoDeletionBar != null) {
1388            if (withAnimation) {
1389                mUndoDeletionBar.animate()
1390                        .setDuration(200)
1391                        .alpha(0f)
1392                        .setListener(new Animator.AnimatorListener() {
1393                            @Override
1394                            public void onAnimationStart(Animator animation) {
1395                                // Do nothing.
1396                            }
1397
1398                            @Override
1399                            public void onAnimationEnd(Animator animation) {
1400                                mUndoDeletionBar.setVisibility(View.GONE);
1401                            }
1402
1403                            @Override
1404                            public void onAnimationCancel(Animator animation) {
1405                                // Do nothing.
1406                            }
1407
1408                            @Override
1409                            public void onAnimationRepeat(Animator animation) {
1410                                // Do nothing.
1411                            }
1412                        })
1413                        .start();
1414            } else {
1415                mUndoDeletionBar.setVisibility(View.GONE);
1416            }
1417        }
1418    }
1419
1420    @Override
1421    public void onShowSwitcherPopup() {
1422    }
1423
1424    /**
1425     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1426     * capture intent.
1427     *
1428     * @param enable {@code true} to enable swipe.
1429     */
1430    public void setSwipingEnabled(boolean enable) {
1431        if (isCaptureIntent()) {
1432            mCameraPreviewData.lockPreview(true);
1433        } else {
1434            mCameraPreviewData.lockPreview(!enable);
1435        }
1436    }
1437
1438    // Accessor methods for getting latency times used in performance testing
1439    public long getAutoFocusTime() {
1440        return (mCurrentModule instanceof PhotoModule) ?
1441                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1442    }
1443
1444    public long getShutterLag() {
1445        return (mCurrentModule instanceof PhotoModule) ?
1446                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1447    }
1448
1449    public long getShutterToPictureDisplayedTime() {
1450        return (mCurrentModule instanceof PhotoModule) ?
1451                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1452    }
1453
1454    public long getPictureDisplayedToJpegCallbackTime() {
1455        return (mCurrentModule instanceof PhotoModule) ?
1456                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1457    }
1458
1459    public long getJpegCallbackFinishTime() {
1460        return (mCurrentModule instanceof PhotoModule) ?
1461                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1462    }
1463
1464    public long getCaptureStartTime() {
1465        return (mCurrentModule instanceof PhotoModule) ?
1466                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1467    }
1468
1469    public boolean isRecording() {
1470        return (mCurrentModule instanceof VideoModule) ?
1471                ((VideoModule) mCurrentModule).isRecording() : false;
1472    }
1473
1474    public CameraOpenErrorCallback getCameraOpenErrorCallback() {
1475        return mCameraOpenErrorCallback;
1476    }
1477}
1478