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