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