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