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