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