PanoramaActivity.java revision a72d73cbac59db43d413291e4db66763be08143a
1/*
2 * Copyright (C) 2011 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.panorama;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.PixelFormat;
22import android.hardware.Camera.Parameters;
23import android.hardware.Camera.Size;
24import android.hardware.Sensor;
25import android.hardware.SensorEvent;
26import android.hardware.SensorEventListener;
27import android.hardware.SensorManager;
28import android.net.Uri;
29import android.os.Bundle;
30import android.util.Log;
31import android.view.View;
32import android.view.WindowManager;
33import android.widget.ImageView;
34
35import com.android.camera.CameraDisabledException;
36import com.android.camera.CameraHardwareException;
37import com.android.camera.CameraHolder;
38import com.android.camera.MenuHelper;
39import com.android.camera.ModePicker;
40import com.android.camera.R;
41import com.android.camera.ShutterButton;
42import com.android.camera.Util;
43
44import java.util.List;
45
46public class PanoramaActivity extends Activity implements
47        ModePicker.OnModeChangeListener {
48    public static final int DEFAULT_SWEEP_ANGLE = 60;
49    public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
50    public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
51
52    private static final String TAG = "PanoramaActivity";
53    private static final float NS2S = 1.0f / 1000000000.0f;  // TODO: commit for this constant.
54
55    private Preview mPreview;
56    private ImageView mReview;
57    private CaptureView mCaptureView;
58    private ShutterButton mShutterButton;
59    private int mPreviewWidth;
60    private int mPreviewHeight;
61    private android.hardware.Camera mCameraDevice;
62    private SensorManager mSensorManager;
63    private Sensor mSensor;
64    private ModePicker mModePicker;
65
66    @Override
67    public void onCreate(Bundle icicle) {
68        super.onCreate(icicle);
69
70        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
71                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
72
73        createContentView();
74
75        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
76
77        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
78        if (mSensor == null) {
79            mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
80        }
81    }
82
83    private void setupCamera() {
84        openCamera();
85        Parameters parameters = mCameraDevice.getParameters();
86        setupCaptureParams(parameters);
87        configureCamera(parameters);
88    }
89
90    private void openCamera() {
91        try {
92            mCameraDevice = Util.openCamera(this, CameraHolder.instance().getBackCameraId());
93        } catch (CameraHardwareException e) {
94            Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
95            return;
96        } catch (CameraDisabledException e) {
97            Util.showErrorAndFinish(this, R.string.camera_disabled);
98            return;
99        }
100    }
101
102    private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
103            boolean needSmaller) {
104        int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
105        boolean hasFound = false;
106        for (Size size: supportedSizes) {
107            int h = size.height;
108            int w = size.width;
109            // we only want 4:3 format.
110            int d = DEFAULT_CAPTURE_PIXELS - h * w;
111            if (needSmaller && d < 0) {  // no bigger preview than 960x720.
112                continue;
113            }
114            if (need4To3 && (h * 4 != w * 3)) {
115                continue;
116            }
117            d = Math.abs(d);
118            if (d < pixelsDiff) {
119                mPreviewWidth = w;
120                mPreviewHeight = h;
121                pixelsDiff = d;
122                hasFound = true;
123            }
124        }
125        return hasFound;
126    }
127
128    private void setupCaptureParams(Parameters parameters) {
129        List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
130        if (!findBestPreviewSize(supportedSizes, true, true)) {
131            Log.w(TAG, "No 4:3 ratio preview size supported.");
132            if (!findBestPreviewSize(supportedSizes, false, true)) {
133                Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
134                findBestPreviewSize(supportedSizes, false, false);
135            }
136        }
137        Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth);
138        parameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
139
140        List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
141        int last = frameRates.size() - 1;
142        int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
143        int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
144        parameters.setPreviewFpsRange(minFps, maxFps);
145        Log.v(TAG, "preview fps: " + minFps + ", " + maxFps);
146    }
147
148    public int getPreviewBufSize() {
149        PixelFormat pixelInfo = new PixelFormat();
150        PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
151        // TODO: remove this extra 32 byte after the driver bug is fixed.
152        return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
153    }
154
155    private void configureCamera(Parameters parameters) {
156        mCameraDevice.setParameters(parameters);
157
158        Util.setCameraDisplayOrientation(
159                Util.getDisplayRotation(this),
160                CameraHolder.instance().getBackCameraId(), mCameraDevice);
161
162        int bufSize = getPreviewBufSize();
163        Log.v(TAG, "BufSize = " + bufSize);
164        for (int i = 0; i < 10; i++) {
165            try {
166                mCameraDevice.addCallbackBuffer(new byte[bufSize]);
167            } catch (OutOfMemoryError e) {
168                Log.v(TAG, "Buffer allocation failed: buffer " + i);
169                break;
170            }
171        }
172    }
173
174    private boolean switchToOtherMode(int mode) {
175        if (isFinishing()) return false;
176        MenuHelper.gotoMode(mode, this);
177        finish();
178        return true;
179    }
180
181    public boolean onModeChanged(int mode) {
182        if (mode != ModePicker.MODE_PANORAMA) {
183            return switchToOtherMode(mode);
184        } else {
185            return true;
186        }
187    }
188
189    private void createContentView() {
190        setContentView(R.layout.panorama);
191
192        mPreview = (Preview) findViewById(R.id.pano_preview);
193        mCaptureView = (CaptureView) findViewById(R.id.pano_capture_view);
194        mCaptureView.setStartAngle(-DEFAULT_SWEEP_ANGLE / 2);
195        mCaptureView.setVisibility(View.INVISIBLE);
196
197        mReview = (ImageView) findViewById(R.id.pano_reviewarea);
198        mReview.setVisibility(View.INVISIBLE);
199
200        mShutterButton = (ShutterButton) findViewById(R.id.pano_shutter_button);
201        mShutterButton.setOnClickListener(new View.OnClickListener() {
202            @Override
203            public void onClick(View v) {
204                mPreview.setCaptureStarted(DEFAULT_SWEEP_ANGLE, DEFAULT_BLEND_MODE);
205            }
206        });
207        mModePicker = (ModePicker) findViewById(R.id.mode_picker);
208        mModePicker.setVisibility(View.VISIBLE);
209        mModePicker.setOnModeChangeListener(this);
210        mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA);
211    }
212
213    public void showResultingMosaic(String uri) {
214        Uri parsed = Uri.parse(uri);
215        mReview.setImageURI(parsed);
216        mReview.setVisibility(View.VISIBLE);
217        mPreview.setVisibility(View.INVISIBLE);
218        mCaptureView.setVisibility(View.INVISIBLE);
219    }
220
221    @Override
222    protected void onPause() {
223        super.onPause();
224        mPreview.onPause();
225        mSensorManager.unregisterListener(mListener);
226        releaseCamera();
227    }
228
229    @Override
230    protected void onResume() {
231        super.onResume();
232
233        /*
234         * It is not necessary to get accelerometer events at a very high
235         * rate, by using a slower rate (SENSOR_DELAY_UI), we get an
236         * automatic low-pass filter, which "extracts" the gravity component
237         * of the acceleration. As an added benefit, we use less power and
238         * CPU resources.
239         */
240        mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI);
241
242        setupCamera();
243        mPreview.setCameraDevice(mCameraDevice);
244        mCameraDevice.startPreview();
245    }
246
247    private void releaseCamera() {
248        if (mCameraDevice != null){
249            CameraHolder.instance().release();
250            mCameraDevice = null;
251        }
252    }
253
254    private final SensorEventListener mListener = new SensorEventListener() {
255        private float mCompassCurrX; // degrees
256        private float mCompassCurrY; // degrees
257        private float mTimestamp;
258
259        public void onSensorChanged(SensorEvent event) {
260
261            if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
262                if (mTimestamp != 0) {
263                    final float dT = (event.timestamp - mTimestamp) * NS2S;
264                    mCompassCurrX += event.values[1] * dT * 180.0f / Math.PI;
265                    mCompassCurrY += event.values[0] * dT * 180.0f / Math.PI;
266                }
267                mTimestamp = event.timestamp;
268
269            } else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
270                mCompassCurrX = event.values[0];
271                mCompassCurrY = event.values[1];
272            }
273
274            if (mPreview != null) {
275                 mPreview.updateCompassValue(mCompassCurrX, mCompassCurrY);
276            }
277        }
278
279        @Override
280        public void onAccuracyChanged(Sensor sensor, int accuracy) {
281        }
282    };
283
284    public int getPreviewFrameWidth() {
285        return mPreviewWidth;
286    }
287
288    public int getPreviewFrameHeight() {
289        return mPreviewHeight;
290    }
291
292    public CaptureView getCaptureView() {
293        return mCaptureView;
294    }
295}
296