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