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