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