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