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