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