CameraActivity.java revision e857cc90a98e280e2008beb03e2446bd642d2ec4
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                mMediaSaveService = null;
157            }};
158
159    // close activity when screen turns off
160    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
161        @Override
162        public void onReceive(Context context, Intent intent) {
163            finish();
164        }
165    };
166
167    private static BroadcastReceiver sScreenOffReceiver;
168    private static class ScreenOffReceiver extends BroadcastReceiver {
169        @Override
170        public void onReceive(Context context, Intent intent) {
171            sFirstStartAfterScreenOn = true;
172        }
173    }
174
175    public static boolean isFirstStartAfterScreenOn() {
176        return sFirstStartAfterScreenOn;
177    }
178
179    public static void resetFirstStartAfterScreenOn() {
180        sFirstStartAfterScreenOn = false;
181    }
182
183    private FilmStripView.Listener mFilmStripListener =
184            new FilmStripView.Listener() {
185                @Override
186                public void onDataPromoted(int dataID) {
187                    removeData(dataID);
188                }
189
190                @Override
191                public void onDataDemoted(int dataID) {
192                    removeData(dataID);
193                }
194
195                @Override
196                public void onDataFullScreenChange(int dataID, boolean full) {
197                }
198
199                @Override
200                public void onSwitchMode(boolean toCamera) {
201                    mCurrentModule.onSwitchMode(toCamera);
202                    if (toCamera) {
203                        mActionBar.hide();
204                    } else {
205                        mActionBar.show();
206                    }
207                }
208
209                @Override
210                public void onCurrentDataChanged(int dataID, boolean current) {
211                    if (!current) {
212                        hidePanoStitchingProgress();
213                    } else {
214                        LocalData currentData = mDataAdapter.getLocalData(dataID);
215                        if (currentData == null) {
216                            Log.w(TAG, "Current data ID not found.");
217                            hidePanoStitchingProgress();
218                            return;
219                        }
220                        updateActionBarMenu(dataID);
221
222                        Uri contentUri = currentData.getContentUri();
223                        if (contentUri == null) {
224                            hidePanoStitchingProgress();
225                            return;
226                        }
227                        int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
228                        if (panoStitchingProgress < 0) {
229                            hidePanoStitchingProgress();
230                            return;
231                        }
232                        showPanoStitchingProgress();
233                        updateStitchingProgress(panoStitchingProgress);
234                    }
235                }
236
237                @Override
238                public boolean onToggleActionBarVisibility() {
239                    if (mActionBar.isShowing()) {
240                        mActionBar.hide();
241                    } else {
242                        mActionBar.show();
243                    }
244                    return mActionBar.isShowing();
245                }
246            };
247
248    private void hidePanoStitchingProgress() {
249        mPanoStitchingPanel.setVisibility(View.GONE);
250    }
251
252    private void showPanoStitchingProgress() {
253        mPanoStitchingPanel.setVisibility(View.VISIBLE);
254    }
255
256    private void updateStitchingProgress(int progress) {
257        mBottomProgress.setProgress(progress);
258    }
259
260    /**
261     * According to the data type, make the menu items for supported operations
262     * visible.
263     * @param dataID the data ID of the current item.
264     */
265    private void updateActionBarMenu(int dataID) {
266        LocalData currentData = mDataAdapter.getLocalData(dataID);
267        int type = currentData.getLocalDataType();
268
269        if (mActionBarMenu == null) {
270            return;
271        }
272
273        int supported = 0;
274        switch (type) {
275            case LocalData.LOCAL_IMAGE:
276                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
277                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
278                        | SUPPORT_SHOW_ON_MAP;
279                break;
280            case LocalData.LOCAL_VIDEO:
281                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM;
282                break;
283            case LocalData.LOCAL_PHOTO_SPHERE:
284                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
285                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
286                        | SUPPORT_SHOW_ON_MAP;
287                break;
288            default:
289                break;
290        }
291
292        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
293                (supported & SUPPORT_DELETE) != 0);
294        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
295                (supported & SUPPORT_ROTATE) != 0);
296        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
297                (supported & SUPPORT_ROTATE) != 0);
298        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
299                (supported & SUPPORT_CROP) != 0);
300        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
301                (supported & SUPPORT_TRIM) != 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                removeData(currentDataId);
441                return true;
442            case R.id.action_edit:
443                launchEditor(localData);
444                return true;
445            case R.id.action_trim:
446                // This is going to be handled by the Gallery app.
447                Intent intent = new Intent(ACTION_TRIM_VIDEO);
448                LocalData currentData = mDataAdapter.getLocalData(
449                        mFilmStripView.getCurrentId());
450                intent.setData(currentData.getContentUri());
451                // We need the file path to wrap this into a RandomAccessFile.
452                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
453                startActivity(intent);
454                return true;
455            case R.id.action_rotate_ccw:
456                // TODO: add the functionality.
457                return true;
458            case R.id.action_rotate_cw:
459                // TODO: add the functionality.
460                return true;
461            case R.id.action_crop:
462                // TODO: add the functionality.
463                return true;
464            case R.id.action_setas:
465                // TODO: add the functionality.
466                return true;
467            case R.id.action_details:
468                (new AsyncTask<Void, Void, MediaDetails>() {
469                    @Override
470                    protected MediaDetails doInBackground(Void... params) {
471                        return localData.getMediaDetails(CameraActivity.this);
472                    }
473
474                    @Override
475                    protected void onPostExecute(MediaDetails mediaDetails) {
476                        DetailsDialog.create(CameraActivity.this, mediaDetails).show();
477                    }
478                }).execute();
479                return true;
480            case R.id.action_show_on_map:
481                double[] latLong = localData.getLatLong();
482                if (latLong != null) {
483                  CameraUtil.showOnMap(this, latLong);
484                }
485                return true;
486            default:
487                return super.onOptionsItemSelected(item);
488        }
489    }
490
491    @Override
492    public void onCreate(Bundle state) {
493        super.onCreate(state);
494        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
495        setContentView(R.layout.camera_filmstrip);
496        mActionBar = getActionBar();
497        // Hide action bar first since we are in full screen mode first.
498        mActionBar.hide();
499
500        if (ApiHelper.HAS_ROTATION_ANIMATION) {
501            setRotationAnimation();
502        }
503        // Check if this is in the secure camera mode.
504        Intent intent = getIntent();
505        String action = intent.getAction();
506        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
507                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
508            mSecureCamera = true;
509        } else {
510            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
511        }
512
513        if (mSecureCamera) {
514            // Change the window flags so that secure camera can show when locked
515            Window win = getWindow();
516            WindowManager.LayoutParams params = win.getAttributes();
517            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
518            win.setAttributes(params);
519
520            // Filter for screen off so that we can finish secure camera activity
521            // when screen is off.
522            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
523            registerReceiver(mScreenOffReceiver, filter);
524            // TODO: This static screen off event receiver is a workaround to the
525            // double onResume() invocation (onResume->onPause->onResume). We should
526            // find a better solution to this.
527            if (sScreenOffReceiver == null) {
528                sScreenOffReceiver = new ScreenOffReceiver();
529                registerReceiver(sScreenOffReceiver, filter);
530            }
531        }
532        mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this);
533        mPanoramaManager.addTaskListener(mStitchingListener);
534        LayoutInflater inflater = getLayoutInflater();
535        View rootLayout = inflater.inflate(R.layout.camera, null, false);
536        mRootView = rootLayout.findViewById(R.id.camera_app_root);
537        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
538        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
539        mCameraPreviewData = new CameraPreviewData(rootLayout,
540                FilmStripView.ImageData.SIZE_FULL,
541                FilmStripView.ImageData.SIZE_FULL);
542        // Put a CameraPreviewData at the first position.
543        mWrappedDataAdapter = new FixedFirstDataAdapter(
544                new CameraDataAdapter(new ColorDrawable(
545                        getResources().getColor(R.color.photo_placeholder))),
546                mCameraPreviewData);
547        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
548        mFilmStripView.setViewGap(
549                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
550        mPanoramaViewHelper = new PanoramaViewHelper(this);
551        mPanoramaViewHelper.onCreate();
552        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
553        // Set up the camera preview first so the preview shows up ASAP.
554        mFilmStripView.setListener(mFilmStripListener);
555        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
556                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
557            mCurrentModule = new VideoModule();
558            mCurrentModuleIndex = CameraSwitcher.VIDEO_MODULE_INDEX;
559        } else {
560            mCurrentModule = new PhotoModule();
561        }
562        mCurrentModule.init(this, mRootView);
563        mOrientationListener = new MyOrientationEventListener(this);
564        mMainHandler = new Handler(getMainLooper());
565        bindMediaSaveService();
566
567        if (!mSecureCamera) {
568            mDataAdapter = mWrappedDataAdapter;
569            mFilmStripView.setDataAdapter(mDataAdapter);
570            mDataAdapter.requestLoad(getContentResolver());
571        } else {
572            // Put a lock placeholder as the last image by setting its date to 0.
573            ImageView v = (ImageView) getLayoutInflater().inflate(
574                    R.layout.secure_album_placeholder, null);
575            mDataAdapter = new FixedLastDataAdapter(
576                    mWrappedDataAdapter,
577                    new SimpleViewData(
578                            v,
579                            v.getDrawable().getIntrinsicWidth(),
580                            v.getDrawable().getIntrinsicHeight(),
581                            0, 0));
582            // Flush out all the original data.
583            mDataAdapter.flush();
584            mFilmStripView.setDataAdapter(mDataAdapter);
585        }
586    }
587
588    private void setRotationAnimation() {
589        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
590        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
591        Window win = getWindow();
592        WindowManager.LayoutParams winParams = win.getAttributes();
593        winParams.rotationAnimation = rotationAnimation;
594        win.setAttributes(winParams);
595    }
596
597    @Override
598    public void onUserInteraction() {
599        super.onUserInteraction();
600        mCurrentModule.onUserInteraction();
601    }
602
603    @Override
604    public void onPause() {
605        mOrientationListener.disable();
606        mCurrentModule.onPauseBeforeSuper();
607        super.onPause();
608        mCurrentModule.onPauseAfterSuper();
609    }
610
611    @Override
612    public void onResume() {
613        if (Settings.System.getInt(getContentResolver(),
614                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
615            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
616            mAutoRotateScreen = false;
617        } else {
618            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
619            mAutoRotateScreen = true;
620        }
621        mOrientationListener.enable();
622        mCurrentModule.onResumeBeforeSuper();
623        super.onResume();
624        mCurrentModule.onResumeAfterSuper();
625
626        setSwipingEnabled(true);
627    }
628
629    @Override
630    public void onStart() {
631        super.onStart();
632        mPanoramaViewHelper.onStart();
633    }
634
635    @Override
636    protected void onStop() {
637        super.onStop();
638        mPanoramaViewHelper.onStop();
639    }
640
641    @Override
642    public void onDestroy() {
643        unbindMediaSaveService();
644        if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
645        super.onDestroy();
646    }
647
648    @Override
649    public void onConfigurationChanged(Configuration config) {
650        super.onConfigurationChanged(config);
651        mCurrentModule.onConfigurationChanged(config);
652    }
653
654    @Override
655    public boolean onKeyDown(int keyCode, KeyEvent event) {
656        if (mCurrentModule.onKeyDown(keyCode, event)) return true;
657        // Prevent software keyboard or voice search from showing up.
658        if (keyCode == KeyEvent.KEYCODE_SEARCH
659                || keyCode == KeyEvent.KEYCODE_MENU) {
660            if (event.isLongPress()) return true;
661        }
662        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
663            return true;
664        }
665
666        return super.onKeyDown(keyCode, event);
667    }
668
669    @Override
670    public boolean onKeyUp(int keyCode, KeyEvent event) {
671        if (mCurrentModule.onKeyUp(keyCode, event)) return true;
672        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
673            return true;
674        }
675        return super.onKeyUp(keyCode, event);
676    }
677
678    public boolean isAutoRotateScreen() {
679        return mAutoRotateScreen;
680    }
681
682    protected void updateStorageSpace() {
683        mStorageSpace = Storage.getAvailableSpace();
684    }
685
686    protected long getStorageSpace() {
687        return mStorageSpace;
688    }
689
690    protected void updateStorageSpaceAndHint() {
691        updateStorageSpace();
692        updateStorageHint(mStorageSpace);
693    }
694
695    protected void updateStorageHint() {
696        updateStorageHint(mStorageSpace);
697    }
698
699    protected boolean updateStorageHintOnResume() {
700        return true;
701    }
702
703    protected void updateStorageHint(long storageSpace) {
704        String message = null;
705        if (storageSpace == Storage.UNAVAILABLE) {
706            message = getString(R.string.no_storage);
707        } else if (storageSpace == Storage.PREPARING) {
708            message = getString(R.string.preparing_sd);
709        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
710            message = getString(R.string.access_sd_fail);
711        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
712            message = getString(R.string.spaceIsLow_content);
713        }
714
715        if (message != null) {
716            if (mStorageHint == null) {
717                mStorageHint = OnScreenHint.makeText(this, message);
718            } else {
719                mStorageHint.setText(message);
720            }
721            mStorageHint.show();
722        } else if (mStorageHint != null) {
723            mStorageHint.cancel();
724            mStorageHint = null;
725        }
726    }
727
728    protected void setResultEx(int resultCode) {
729        mResultCodeForTesting = resultCode;
730        setResult(resultCode);
731    }
732
733    protected void setResultEx(int resultCode, Intent data) {
734        mResultCodeForTesting = resultCode;
735        mResultDataForTesting = data;
736        setResult(resultCode, data);
737    }
738
739    public int getResultCode() {
740        return mResultCodeForTesting;
741    }
742
743    public Intent getResultData() {
744        return mResultDataForTesting;
745    }
746
747    public boolean isSecureCamera() {
748        return mSecureCamera;
749    }
750
751    @Override
752    public void onCameraSelected(int i) {
753        if (mCurrentModuleIndex == i) return;
754
755        CameraHolder.instance().keep();
756        closeModule(mCurrentModule);
757        mCurrentModuleIndex = i;
758        switch (i) {
759            case CameraSwitcher.VIDEO_MODULE_INDEX:
760                mCurrentModule = new VideoModule();
761                break;
762            case CameraSwitcher.PHOTO_MODULE_INDEX:
763                mCurrentModule = new PhotoModule();
764                break;
765            case CameraSwitcher.LIGHTCYCLE_MODULE_INDEX:
766                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
767                break;
768           default:
769               break;
770        }
771
772        openModule(mCurrentModule);
773        mCurrentModule.onOrientationChanged(mLastRawOrientation);
774        if (mMediaSaveService != null) {
775            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
776        }
777    }
778
779    /**
780     * Launches an ACTION_EDIT intent for the given local data item.
781     */
782    public void launchEditor(LocalData data) {
783        Intent intent = new Intent(Intent.ACTION_EDIT)
784                .setDataAndType(data.getContentUri(), data.getMimeType())
785                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
786        startActivity(Intent.createChooser(intent, null));
787    }
788
789    private void openModule(CameraModule module) {
790        module.init(this, mRootView);
791        module.onResumeBeforeSuper();
792        module.onResumeAfterSuper();
793    }
794
795    private void closeModule(CameraModule module) {
796        module.onPauseBeforeSuper();
797        module.onPauseAfterSuper();
798        ((ViewGroup) mRootView).removeAllViews();
799    }
800
801    @Override
802    public void onShowSwitcherPopup() {
803    }
804
805    public void setSwipingEnabled(boolean enable) {
806        mCameraPreviewData.lockPreview(!enable);
807    }
808
809    // Accessor methods for getting latency times used in performance testing
810    public long getAutoFocusTime() {
811        return (mCurrentModule instanceof PhotoModule) ?
812                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
813    }
814
815    public long getShutterLag() {
816        return (mCurrentModule instanceof PhotoModule) ?
817                ((PhotoModule) mCurrentModule).mShutterLag : -1;
818    }
819
820    public long getShutterToPictureDisplayedTime() {
821        return (mCurrentModule instanceof PhotoModule) ?
822                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
823    }
824
825    public long getPictureDisplayedToJpegCallbackTime() {
826        return (mCurrentModule instanceof PhotoModule) ?
827                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
828    }
829
830    public long getJpegCallbackFinishTime() {
831        return (mCurrentModule instanceof PhotoModule) ?
832                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
833    }
834
835    public long getCaptureStartTime() {
836        return (mCurrentModule instanceof PhotoModule) ?
837                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
838    }
839
840    public boolean isRecording() {
841        return (mCurrentModule instanceof VideoModule) ?
842                ((VideoModule) mCurrentModule).isRecording() : false;
843    }
844}
845