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