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