CameraActivity.java revision 50df47071c776703a36dbc4cb90ec8d1df3c0f6e
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.ActionBar;
20import android.app.Activity;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.ServiceConnection;
28import android.content.pm.ActivityInfo;
29import android.content.res.Configuration;
30import android.graphics.drawable.ColorDrawable;
31import android.net.Uri;
32import android.os.Bundle;
33import android.os.Handler;
34import android.os.IBinder;
35import android.provider.MediaStore;
36import android.provider.Settings;
37import android.util.Log;
38import android.view.KeyEvent;
39import android.view.LayoutInflater;
40import android.view.Menu;
41import android.view.MenuInflater;
42import android.view.MenuItem;
43import android.view.OrientationEventListener;
44import android.view.View;
45import android.view.ViewGroup;
46import android.view.Window;
47import android.view.WindowManager;
48import android.widget.ImageView;
49import android.widget.ProgressBar;
50
51import com.android.camera.data.CameraDataAdapter;
52import com.android.camera.data.CameraPreviewData;
53import com.android.camera.data.FixedFirstDataAdapter;
54import com.android.camera.data.FixedLastDataAdapter;
55import com.android.camera.data.LocalData;
56import com.android.camera.data.LocalDataAdapter;
57import com.android.camera.data.SimpleViewData;
58import com.android.camera.util.ApiHelper;
59import com.android.camera.ui.CameraSwitcher;
60import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
61import com.android.camera.ui.FilmStripView;
62import com.android.camera.util.CameraUtil;
63import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
64import com.android.camera.util.PhotoSphereHelper;
65import com.android.camera2.R;
66
67public class CameraActivity extends Activity
68    implements CameraSwitchListener {
69
70    private static final String TAG = "CAM_Activity";
71
72    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
73            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
74    public static final String ACTION_IMAGE_CAPTURE_SECURE =
75            "android.media.action.IMAGE_CAPTURE_SECURE";
76
77    // The intent extra for camera from secure lock screen. True if the gallery
78    // should only show newly captured pictures. sSecureAlbumId does not
79    // increment. This is used when switching between camera, camcorder, and
80    // panorama. If the extra is not set, it is in the normal camera mode.
81    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
82
83    // Supported operations at FilmStripView. Different data has different
84    // set of supported operations.
85    private static final int SUPPORT_DELETE = 1 << 0;
86    private static final int SUPPORT_ROTATE = 1 << 1;
87    private static final int SUPPORT_INFO = 1 << 2;
88    private static final int SUPPORT_CROP = 1 << 3;
89    private static final int SUPPORT_SETAS = 1 << 4;
90    private static final int SUPPORT_EDIT = 1 << 5;
91    private static final int SUPPORT_TRIM = 1 << 6;
92    private static final int SUPPORT_MUTE = 1 << 7;
93    private static final int SUPPORT_SHOW_ON_MAP = 1 << 8;
94    private static final int SUPPORT_ALL = 0xffffffff;
95
96    /** This data adapter is used by FilmStirpView. */
97    private LocalDataAdapter mDataAdapter;
98    /** This data adapter represents the real local camera data. */
99    private LocalDataAdapter mWrappedDataAdapter;
100
101    private PanoramaStitchingManager mPanoramaManager;
102    private int mCurrentModuleIndex;
103    private CameraModule mCurrentModule;
104    private View mRootView;
105    private FilmStripView mFilmStripView;
106    private ProgressBar mBottomProgress;
107    private View mPanoStitchingPanel;
108    private int mResultCodeForTesting;
109    private Intent mResultDataForTesting;
110    private OnScreenHint mStorageHint;
111    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
112    private boolean mAutoRotateScreen;
113    private boolean mSecureCamera;
114    // This is a hack to speed up the start of SecureCamera.
115    private static boolean sFirstStartAfterScreenOn = true;
116    private boolean mShowCameraPreview;
117    private int mLastRawOrientation;
118    private MyOrientationEventListener mOrientationListener;
119    private Handler mMainHandler;
120    private PanoramaViewHelper mPanoramaViewHelper;
121    private CameraPreviewData mCameraPreviewData;
122    private ActionBar mActionBar;
123    private Menu mActionBarMenu;
124
125    private class MyOrientationEventListener
126        extends OrientationEventListener {
127        public MyOrientationEventListener(Context context) {
128            super(context);
129        }
130
131        @Override
132        public void onOrientationChanged(int orientation) {
133            // We keep the last known orientation. So if the user first orient
134            // the camera then point the camera to floor or sky, we still have
135            // the correct orientation.
136            if (orientation == ORIENTATION_UNKNOWN) return;
137            mLastRawOrientation = orientation;
138            mCurrentModule.onOrientationChanged(orientation);
139        }
140    }
141
142    private MediaSaveService mMediaSaveService;
143    private ServiceConnection mConnection = new ServiceConnection() {
144            @Override
145            public void onServiceConnected(ComponentName className, IBinder b) {
146                mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
147                mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
148            }
149            @Override
150            public void onServiceDisconnected(ComponentName className) {
151                mMediaSaveService = null;
152            }};
153
154    // close activity when screen turns off
155    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
156        @Override
157        public void onReceive(Context context, Intent intent) {
158            finish();
159        }
160    };
161
162    private static BroadcastReceiver sScreenOffReceiver;
163    private static class ScreenOffReceiver extends BroadcastReceiver {
164        @Override
165        public void onReceive(Context context, Intent intent) {
166            sFirstStartAfterScreenOn = true;
167        }
168    }
169
170    public static boolean isFirstStartAfterScreenOn() {
171        return sFirstStartAfterScreenOn;
172    }
173
174    public static void resetFirstStartAfterScreenOn() {
175        sFirstStartAfterScreenOn = false;
176    }
177
178    private FilmStripView.Listener mFilmStripListener =
179            new FilmStripView.Listener() {
180                @Override
181                public void onDataPromoted(int dataID) {
182                    removeData(dataID);
183                }
184
185                @Override
186                public void onDataDemoted(int dataID) {
187                    removeData(dataID);
188                }
189
190                @Override
191                public void onDataFullScreenChange(int dataID, boolean full) {
192                }
193
194                @Override
195                public void onSwitchMode(boolean toCamera) {
196                    mCurrentModule.onSwitchMode(toCamera);
197                    if (toCamera) {
198                        mActionBar.hide();
199                    } else {
200                        mActionBar.show();
201                    }
202                }
203
204                @Override
205                public void onCurrentDataChanged(int dataID, boolean current) {
206                    if (!current) {
207                        hidePanoStitchingProgress();
208                    } else {
209                        LocalData currentData = mDataAdapter.getLocalData(dataID);
210                        if (currentData == null) {
211                            Log.w(TAG, "Current data ID not found.");
212                            hidePanoStitchingProgress();
213                            return;
214                        }
215                        int type = currentData.getLocalDataType(dataID);
216                        updateActionBarMenu(type);
217
218                        Uri contentUri = currentData.getContentUri();
219                        if (contentUri == null) {
220                            hidePanoStitchingProgress();
221                            return;
222                        }
223                        int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
224                        if (panoStitchingProgress < 0) {
225                            hidePanoStitchingProgress();
226                            return;
227                        }
228                        showPanoStitchingProgress();
229                        updateStitchingProgress(panoStitchingProgress);
230                    }
231                }
232            };
233
234    private void hidePanoStitchingProgress() {
235        mPanoStitchingPanel.setVisibility(View.GONE);
236    }
237
238    private void showPanoStitchingProgress() {
239        mPanoStitchingPanel.setVisibility(View.VISIBLE);
240    }
241
242    private void updateStitchingProgress(int progress) {
243        mBottomProgress.setProgress(progress);
244    }
245
246    /**
247     * According to the data type, make the menu items for supported operations
248     * visible.
249     * @param type : the data type for the current local data.
250     */
251    private void updateActionBarMenu(int type) {
252        if (mActionBarMenu == null) {
253            return;
254        }
255
256        int supported = 0;
257        switch (type) {
258            case LocalData.LOCAL_IMAGE:
259                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
260                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
261                        | SUPPORT_SHOW_ON_MAP;
262                break;
263            case LocalData.LOCAL_VIDEO:
264                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
265                        | SUPPORT_MUTE;
266                break;
267            case LocalData.LOCAL_PHOTO_SPHERE:
268                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
269                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
270                        | SUPPORT_SHOW_ON_MAP;
271                break;
272            default:
273                break;
274        }
275
276        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
277                (supported & SUPPORT_DELETE) != 0);
278        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
279                (supported & SUPPORT_ROTATE) != 0);
280        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
281                (supported & SUPPORT_ROTATE) != 0);
282        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
283                (supported & SUPPORT_CROP) != 0);
284        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
285                (supported & SUPPORT_TRIM) != 0);
286        setMenuItemVisible(mActionBarMenu, R.id.action_mute,
287                (supported & SUPPORT_MUTE) != 0);
288        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
289                (supported & SUPPORT_SETAS) != 0);
290        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
291                (supported & SUPPORT_SHOW_ON_MAP) != 0);
292        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
293                (supported & SUPPORT_EDIT) != 0);
294        setMenuItemVisible(mActionBarMenu, R.id.action_details,
295                (supported & SUPPORT_INFO) != 0);
296    }
297
298    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
299        MenuItem item = menu.findItem(itemId);
300        if (item != null)
301            item.setVisible(visible);
302    }
303
304    private Runnable mDeletionRunnable = new Runnable() {
305            @Override
306            public void run() {
307                mDataAdapter.executeDeletion(CameraActivity.this);
308            }
309        };
310
311    private ImageTaskManager.TaskListener mStitchingListener =
312            new ImageTaskManager.TaskListener() {
313                @Override
314                public void onTaskQueued(String filePath, final Uri imageUri) {
315                    mMainHandler.post(new Runnable() {
316                        @Override
317                        public void run() {
318                            notifyNewMedia(imageUri);
319                        }
320                    });
321                }
322
323                @Override
324                public void onTaskDone(String filePath, final Uri imageUri) {
325                    Log.v(TAG, "onTaskDone:" + filePath);
326                    mMainHandler.post(new Runnable() {
327                        @Override
328                        public void run() {
329                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
330                            int currentDataId = mFilmStripView.getCurrentId();
331
332                            if (currentDataId == doneID) {
333                                hidePanoStitchingProgress();
334                                updateStitchingProgress(0);
335                            }
336
337                            mDataAdapter.refresh(getContentResolver(), imageUri);
338                        }
339                    });
340                }
341
342                @Override
343                public void onTaskProgress(
344                        String filePath, final Uri imageUri, final int progress) {
345                    mMainHandler.post(new Runnable() {
346                        @Override
347                        public void run() {
348                            int currentDataId = mFilmStripView.getCurrentId();
349                            if (currentDataId == -1) {
350                                return;
351                            }
352                            if (imageUri.equals(
353                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
354                                updateStitchingProgress(progress);
355                            }
356                        }
357                    });
358                }
359            };
360
361    public MediaSaveService getMediaSaveService() {
362        return mMediaSaveService;
363    }
364
365    public void notifyNewMedia(Uri uri) {
366        ContentResolver cr = getContentResolver();
367        String mimeType = cr.getType(uri);
368        if (mimeType.startsWith("video/")) {
369            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
370            mDataAdapter.addNewVideo(cr, uri);
371        } else if (mimeType.startsWith("image/")) {
372            CameraUtil.broadcastNewPicture(this, uri);
373            mDataAdapter.addNewPhoto(cr, uri);
374        } else if (mimeType.startsWith("application/stitching-preview")) {
375            mDataAdapter.addNewPhoto(cr, uri);
376        } else {
377            android.util.Log.w(TAG, "Unknown new media with MIME type:"
378                    + mimeType + ", uri:" + uri);
379        }
380    }
381
382    private void removeData(int dataID) {
383        mDataAdapter.removeData(CameraActivity.this, dataID);
384        mMainHandler.removeCallbacks(mDeletionRunnable);
385        mMainHandler.postDelayed(mDeletionRunnable, 3000);
386    }
387
388    private void bindMediaSaveService() {
389        Intent intent = new Intent(this, MediaSaveService.class);
390        startService(intent);  // start service before binding it so the
391                               // service won't be killed if we unbind it.
392        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
393    }
394
395    private void unbindMediaSaveService() {
396        if (mMediaSaveService != null) {
397            mMediaSaveService.setListener(null);
398        }
399        if (mConnection != null) {
400            unbindService(mConnection);
401        }
402    }
403
404    @Override
405    public boolean onCreateOptionsMenu(Menu menu) {
406        // Inflate the menu items for use in the action bar
407        MenuInflater inflater = getMenuInflater();
408        inflater.inflate(R.menu.operations, menu);
409        mActionBarMenu = menu;
410        return super.onCreateOptionsMenu(menu);
411    }
412
413    @Override
414    public boolean onOptionsItemSelected(MenuItem item) {
415        // Handle presses on the action bar items
416        switch (item.getItemId()) {
417            case R.id.action_delete:
418                // TODO: add the functionality.
419                return true;
420            case R.id.action_edit:
421                // TODO: add the functionality.
422                return true;
423            case R.id.action_trim:
424                // TODO: add the functionality.
425                return true;
426            case R.id.action_mute:
427                // TODO: add the functionality.
428                return true;
429            case R.id.action_rotate_ccw:
430                // TODO: add the functionality.
431                return true;
432            case R.id.action_rotate_cw:
433                // TODO: add the functionality.
434                return true;
435            case R.id.action_crop:
436                // TODO: add the functionality.
437                return true;
438            case R.id.action_setas:
439                // TODO: add the functionality.
440                return true;
441            case R.id.action_details:
442                // TODO: add the functionality.
443                return true;
444            case R.id.action_show_on_map:
445                // TODO: add the functionality.
446                return true;
447            default:
448                return super.onOptionsItemSelected(item);
449        }
450    }
451
452    @Override
453    public void onCreate(Bundle state) {
454        super.onCreate(state);
455        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
456        setContentView(R.layout.camera_filmstrip);
457        mActionBar = getActionBar();
458        // Hide action bar first since we are in full screen mode first.
459        mActionBar.hide();
460
461        if (ApiHelper.HAS_ROTATION_ANIMATION) {
462            setRotationAnimation();
463        }
464        // Check if this is in the secure camera mode.
465        Intent intent = getIntent();
466        String action = intent.getAction();
467        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
468                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
469            mSecureCamera = true;
470        } else {
471            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
472        }
473
474        if (mSecureCamera) {
475            // Change the window flags so that secure camera can show when locked
476            Window win = getWindow();
477            WindowManager.LayoutParams params = win.getAttributes();
478            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
479            win.setAttributes(params);
480
481            // Filter for screen off so that we can finish secure camera activity
482            // when screen is off.
483            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
484            registerReceiver(mScreenOffReceiver, filter);
485            // TODO: This static screen off event receiver is a workaround to the
486            // double onResume() invocation (onResume->onPause->onResume). We should
487            // find a better solution to this.
488            if (sScreenOffReceiver == null) {
489                sScreenOffReceiver = new ScreenOffReceiver();
490                registerReceiver(sScreenOffReceiver, filter);
491            }
492        }
493        mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this);
494        mPanoramaManager.addTaskListener(mStitchingListener);
495        LayoutInflater inflater = getLayoutInflater();
496        View rootLayout = inflater.inflate(R.layout.camera, null, false);
497        mRootView = rootLayout.findViewById(R.id.camera_app_root);
498        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
499        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
500        mCameraPreviewData = new CameraPreviewData(rootLayout,
501                FilmStripView.ImageData.SIZE_FULL,
502                FilmStripView.ImageData.SIZE_FULL);
503        // Put a CameraPreviewData at the first position.
504        mWrappedDataAdapter = new FixedFirstDataAdapter(
505                new CameraDataAdapter(new ColorDrawable(
506                        getResources().getColor(R.color.photo_placeholder))),
507                mCameraPreviewData);
508        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
509        mFilmStripView.setViewGap(
510                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
511        mPanoramaViewHelper = new PanoramaViewHelper(this);
512        mPanoramaViewHelper.onCreate();
513        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
514        // Set up the camera preview first so the preview shows up ASAP.
515        mFilmStripView.setListener(mFilmStripListener);
516        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
517                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
518            mCurrentModule = new VideoModule();
519            mCurrentModuleIndex = CameraSwitcher.VIDEO_MODULE_INDEX;
520        } else {
521            mCurrentModule = new PhotoModule();
522        }
523        mCurrentModule.init(this, mRootView);
524        mOrientationListener = new MyOrientationEventListener(this);
525        mMainHandler = new Handler(getMainLooper());
526        bindMediaSaveService();
527
528        if (!mSecureCamera) {
529            mDataAdapter = mWrappedDataAdapter;
530            mDataAdapter.requestLoad(getContentResolver());
531        } else {
532            // Put a lock placeholder as the last image by setting its date to 0.
533            ImageView v = (ImageView) getLayoutInflater().inflate(
534                    R.layout.secure_album_placeholder, null);
535            mDataAdapter = new FixedLastDataAdapter(
536                    mWrappedDataAdapter,
537                    new SimpleViewData(
538                            v,
539                            v.getDrawable().getIntrinsicWidth(),
540                            v.getDrawable().getIntrinsicHeight(),
541                            0, 0));
542            // Flush out all the original data.
543            mDataAdapter.flush();
544        }
545        mFilmStripView.setDataAdapter(mDataAdapter);
546    }
547
548    private void setRotationAnimation() {
549        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
550        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
551        Window win = getWindow();
552        WindowManager.LayoutParams winParams = win.getAttributes();
553        winParams.rotationAnimation = rotationAnimation;
554        win.setAttributes(winParams);
555    }
556
557    @Override
558    public void onUserInteraction() {
559        super.onUserInteraction();
560        mCurrentModule.onUserInteraction();
561    }
562
563    @Override
564    public void onPause() {
565        mOrientationListener.disable();
566        mCurrentModule.onPauseBeforeSuper();
567        super.onPause();
568        mCurrentModule.onPauseAfterSuper();
569    }
570
571    @Override
572    public void onResume() {
573        if (Settings.System.getInt(getContentResolver(),
574                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
575            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
576            mAutoRotateScreen = false;
577        } else {
578            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
579            mAutoRotateScreen = true;
580        }
581        mOrientationListener.enable();
582        mCurrentModule.onResumeBeforeSuper();
583        super.onResume();
584        mCurrentModule.onResumeAfterSuper();
585
586        setSwipingEnabled(true);
587    }
588
589    @Override
590    public void onStart() {
591        super.onStart();
592
593        mPanoramaViewHelper.onStart();
594    }
595
596    @Override
597    protected void onStop() {
598        super.onStop();
599        mPanoramaViewHelper.onStop();
600    }
601
602    @Override
603    public void onDestroy() {
604        unbindMediaSaveService();
605        if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
606        super.onDestroy();
607    }
608
609    @Override
610    public void onConfigurationChanged(Configuration config) {
611        super.onConfigurationChanged(config);
612        mCurrentModule.onConfigurationChanged(config);
613    }
614
615    @Override
616    public boolean onKeyDown(int keyCode, KeyEvent event) {
617        if (mCurrentModule.onKeyDown(keyCode, event)) return true;
618        // Prevent software keyboard or voice search from showing up.
619        if (keyCode == KeyEvent.KEYCODE_SEARCH
620                || keyCode == KeyEvent.KEYCODE_MENU) {
621            if (event.isLongPress()) return true;
622        }
623        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
624            return true;
625        }
626
627        return super.onKeyDown(keyCode, event);
628    }
629
630    @Override
631    public boolean onKeyUp(int keyCode, KeyEvent event) {
632        if (mCurrentModule.onKeyUp(keyCode, event)) return true;
633        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
634            return true;
635        }
636        return super.onKeyUp(keyCode, event);
637    }
638
639    public boolean isAutoRotateScreen() {
640        return mAutoRotateScreen;
641    }
642
643    protected void updateStorageSpace() {
644        mStorageSpace = Storage.getAvailableSpace();
645    }
646
647    protected long getStorageSpace() {
648        return mStorageSpace;
649    }
650
651    protected void updateStorageSpaceAndHint() {
652        updateStorageSpace();
653        updateStorageHint(mStorageSpace);
654    }
655
656    protected void updateStorageHint() {
657        updateStorageHint(mStorageSpace);
658    }
659
660    protected boolean updateStorageHintOnResume() {
661        return true;
662    }
663
664    protected void updateStorageHint(long storageSpace) {
665        String message = null;
666        if (storageSpace == Storage.UNAVAILABLE) {
667            message = getString(R.string.no_storage);
668        } else if (storageSpace == Storage.PREPARING) {
669            message = getString(R.string.preparing_sd);
670        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
671            message = getString(R.string.access_sd_fail);
672        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
673            message = getString(R.string.spaceIsLow_content);
674        }
675
676        if (message != null) {
677            if (mStorageHint == null) {
678                mStorageHint = OnScreenHint.makeText(this, message);
679            } else {
680                mStorageHint.setText(message);
681            }
682            mStorageHint.show();
683        } else if (mStorageHint != null) {
684            mStorageHint.cancel();
685            mStorageHint = null;
686        }
687    }
688
689    protected void setResultEx(int resultCode) {
690        mResultCodeForTesting = resultCode;
691        setResult(resultCode);
692    }
693
694    protected void setResultEx(int resultCode, Intent data) {
695        mResultCodeForTesting = resultCode;
696        mResultDataForTesting = data;
697        setResult(resultCode, data);
698    }
699
700    public int getResultCode() {
701        return mResultCodeForTesting;
702    }
703
704    public Intent getResultData() {
705        return mResultDataForTesting;
706    }
707
708    public boolean isSecureCamera() {
709        return mSecureCamera;
710    }
711
712    @Override
713    public void onCameraSelected(int i) {
714        if (mCurrentModuleIndex == i) return;
715
716        CameraHolder.instance().keep();
717        closeModule(mCurrentModule);
718        mCurrentModuleIndex = i;
719        switch (i) {
720            case CameraSwitcher.VIDEO_MODULE_INDEX:
721                mCurrentModule = new VideoModule();
722                break;
723            case CameraSwitcher.PHOTO_MODULE_INDEX:
724                mCurrentModule = new PhotoModule();
725                break;
726            case CameraSwitcher.LIGHTCYCLE_MODULE_INDEX:
727                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
728                break;
729           default:
730               break;
731        }
732
733        openModule(mCurrentModule);
734        mCurrentModule.onOrientationChanged(mLastRawOrientation);
735        if (mMediaSaveService != null) {
736            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
737        }
738    }
739
740    private void openModule(CameraModule module) {
741        module.init(this, mRootView);
742        module.onResumeBeforeSuper();
743        module.onResumeAfterSuper();
744    }
745
746    private void closeModule(CameraModule module) {
747        module.onPauseBeforeSuper();
748        module.onPauseAfterSuper();
749        ((ViewGroup) mRootView).removeAllViews();
750    }
751
752    @Override
753    public void onShowSwitcherPopup() {
754    }
755
756    public void setSwipingEnabled(boolean enable) {
757        mCameraPreviewData.lockPreview(!enable);
758    }
759
760    // Accessor methods for getting latency times used in performance testing
761    public long getAutoFocusTime() {
762        return (mCurrentModule instanceof PhotoModule) ?
763                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
764    }
765
766    public long getShutterLag() {
767        return (mCurrentModule instanceof PhotoModule) ?
768                ((PhotoModule) mCurrentModule).mShutterLag : -1;
769    }
770
771    public long getShutterToPictureDisplayedTime() {
772        return (mCurrentModule instanceof PhotoModule) ?
773                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
774    }
775
776    public long getPictureDisplayedToJpegCallbackTime() {
777        return (mCurrentModule instanceof PhotoModule) ?
778                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
779    }
780
781    public long getJpegCallbackFinishTime() {
782        return (mCurrentModule instanceof PhotoModule) ?
783                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
784    }
785
786    public long getCaptureStartTime() {
787        return (mCurrentModule instanceof PhotoModule) ?
788                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
789    }
790
791    public boolean isRecording() {
792        return (mCurrentModule instanceof VideoModule) ?
793                ((VideoModule) mCurrentModule).isRecording() : false;
794    }
795}
796