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