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