1 /* 2 * Copyright (C) 2007 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 17 package com.example.android.apis.graphics; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.Context; 22 import android.hardware.Camera; 23 import android.hardware.Camera.CameraInfo; 24 import android.hardware.Camera.Size; 25 import android.os.Bundle; 26 import android.util.Log; 27 import android.view.Menu; 28 import android.view.MenuInflater; 29 import android.view.MenuItem; 30 import android.view.SurfaceHolder; 31 import android.view.SurfaceView; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.Window; 35 import android.view.WindowManager; 36 37 import java.io.IOException; 38 import java.util.List; 39 40 // Need the following import to get access to the app resources, since this 41 // class is in a sub-package. 42 import com.example.android.apis.R; 43 44 // ---------------------------------------------------------------------- 45 46 public class CameraPreview extends Activity { 47 private Preview mPreview; 48 Camera mCamera; 49 int numberOfCameras; 50 int cameraCurrentlyLocked; 51 52 // The first rear facing camera 53 int defaultCameraId; 54 55 @Override onCreate(Bundle savedInstanceState)56 protected void onCreate(Bundle savedInstanceState) { 57 super.onCreate(savedInstanceState); 58 59 // Hide the window title. 60 requestWindowFeature(Window.FEATURE_NO_TITLE); 61 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 62 63 // Create a RelativeLayout container that will hold a SurfaceView, 64 // and set it as the content of our activity. 65 mPreview = new Preview(this); 66 setContentView(mPreview); 67 68 // Find the total number of cameras available 69 numberOfCameras = Camera.getNumberOfCameras(); 70 71 // Find the ID of the default camera 72 CameraInfo cameraInfo = new CameraInfo(); 73 for (int i = 0; i < numberOfCameras; i++) { 74 Camera.getCameraInfo(i, cameraInfo); 75 if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { 76 defaultCameraId = i; 77 } 78 } 79 } 80 81 @Override onResume()82 protected void onResume() { 83 super.onResume(); 84 85 // Open the default i.e. the first rear facing camera. 86 mCamera = Camera.open(); 87 cameraCurrentlyLocked = defaultCameraId; 88 mPreview.setCamera(mCamera); 89 } 90 91 @Override onPause()92 protected void onPause() { 93 super.onPause(); 94 95 // Because the Camera object is a shared resource, it's very 96 // important to release it when the activity is paused. 97 if (mCamera != null) { 98 mPreview.setCamera(null); 99 mCamera.release(); 100 mCamera = null; 101 } 102 } 103 104 @Override onCreateOptionsMenu(Menu menu)105 public boolean onCreateOptionsMenu(Menu menu) { 106 107 // Inflate our menu which can gather user input for switching camera 108 MenuInflater inflater = getMenuInflater(); 109 inflater.inflate(R.menu.camera_menu, menu); 110 return true; 111 } 112 113 @Override onOptionsItemSelected(MenuItem item)114 public boolean onOptionsItemSelected(MenuItem item) { 115 // Handle item selection 116 switch (item.getItemId()) { 117 case R.id.switch_cam: 118 // check for availability of multiple cameras 119 if (numberOfCameras == 1) { 120 AlertDialog.Builder builder = new AlertDialog.Builder(this); 121 builder.setMessage(this.getString(R.string.camera_alert)) 122 .setNeutralButton("Close", null); 123 AlertDialog alert = builder.create(); 124 alert.show(); 125 return true; 126 } 127 128 // OK, we have multiple cameras. 129 // Release this camera -> cameraCurrentlyLocked 130 if (mCamera != null) { 131 mCamera.stopPreview(); 132 mPreview.setCamera(null); 133 mCamera.release(); 134 mCamera = null; 135 } 136 137 // Acquire the next camera and request Preview to reconfigure 138 // parameters. 139 mCamera = Camera 140 .open((cameraCurrentlyLocked + 1) % numberOfCameras); 141 cameraCurrentlyLocked = (cameraCurrentlyLocked + 1) 142 % numberOfCameras; 143 mPreview.switchCamera(mCamera); 144 145 // Start the preview 146 mCamera.startPreview(); 147 return true; 148 default: 149 return super.onOptionsItemSelected(item); 150 } 151 } 152 } 153 154 // ---------------------------------------------------------------------- 155 156 /** 157 * A simple wrapper around a Camera and a SurfaceView that renders a centered preview of the Camera 158 * to the surface. We need to center the SurfaceView because not all devices have cameras that 159 * support preview sizes at the same aspect ratio as the device's display. 160 */ 161 class Preview extends ViewGroup implements SurfaceHolder.Callback { 162 private final String TAG = "Preview"; 163 164 SurfaceView mSurfaceView; 165 SurfaceHolder mHolder; 166 Size mPreviewSize; 167 List<Size> mSupportedPreviewSizes; 168 Camera mCamera; 169 Preview(Context context)170 Preview(Context context) { 171 super(context); 172 173 mSurfaceView = new SurfaceView(context); 174 addView(mSurfaceView); 175 176 // Install a SurfaceHolder.Callback so we get notified when the 177 // underlying surface is created and destroyed. 178 mHolder = mSurfaceView.getHolder(); 179 mHolder.addCallback(this); 180 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 181 } 182 setCamera(Camera camera)183 public void setCamera(Camera camera) { 184 mCamera = camera; 185 if (mCamera != null) { 186 mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes(); 187 requestLayout(); 188 } 189 } 190 switchCamera(Camera camera)191 public void switchCamera(Camera camera) { 192 setCamera(camera); 193 try { 194 camera.setPreviewDisplay(mHolder); 195 } catch (IOException exception) { 196 Log.e(TAG, "IOException caused by setPreviewDisplay()", exception); 197 } 198 Camera.Parameters parameters = camera.getParameters(); 199 parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); 200 requestLayout(); 201 202 camera.setParameters(parameters); 203 } 204 205 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)206 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 207 // We purposely disregard child measurements because act as a 208 // wrapper to a SurfaceView that centers the camera preview instead 209 // of stretching it. 210 final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); 211 final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); 212 setMeasuredDimension(width, height); 213 214 if (mSupportedPreviewSizes != null) { 215 mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height); 216 } 217 } 218 219 @Override onLayout(boolean changed, int l, int t, int r, int b)220 protected void onLayout(boolean changed, int l, int t, int r, int b) { 221 if (changed && getChildCount() > 0) { 222 final View child = getChildAt(0); 223 224 final int width = r - l; 225 final int height = b - t; 226 227 int previewWidth = width; 228 int previewHeight = height; 229 if (mPreviewSize != null) { 230 previewWidth = mPreviewSize.width; 231 previewHeight = mPreviewSize.height; 232 } 233 234 // Center the child SurfaceView within the parent. 235 if (width * previewHeight > height * previewWidth) { 236 final int scaledChildWidth = previewWidth * height / previewHeight; 237 child.layout((width - scaledChildWidth) / 2, 0, 238 (width + scaledChildWidth) / 2, height); 239 } else { 240 final int scaledChildHeight = previewHeight * width / previewWidth; 241 child.layout(0, (height - scaledChildHeight) / 2, 242 width, (height + scaledChildHeight) / 2); 243 } 244 } 245 } 246 surfaceCreated(SurfaceHolder holder)247 public void surfaceCreated(SurfaceHolder holder) { 248 // The Surface has been created, acquire the camera and tell it where 249 // to draw. 250 try { 251 if (mCamera != null) { 252 mCamera.setPreviewDisplay(holder); 253 } 254 } catch (IOException exception) { 255 Log.e(TAG, "IOException caused by setPreviewDisplay()", exception); 256 } 257 } 258 surfaceDestroyed(SurfaceHolder holder)259 public void surfaceDestroyed(SurfaceHolder holder) { 260 // Surface will be destroyed when we return, so stop the preview. 261 if (mCamera != null) { 262 mCamera.stopPreview(); 263 } 264 } 265 266 getOptimalPreviewSize(List<Size> sizes, int w, int h)267 private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) { 268 final double ASPECT_TOLERANCE = 0.1; 269 double targetRatio = (double) w / h; 270 if (sizes == null) return null; 271 272 Size optimalSize = null; 273 double minDiff = Double.MAX_VALUE; 274 275 int targetHeight = h; 276 277 // Try to find an size match aspect ratio and size 278 for (Size size : sizes) { 279 double ratio = (double) size.width / size.height; 280 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; 281 if (Math.abs(size.height - targetHeight) < minDiff) { 282 optimalSize = size; 283 minDiff = Math.abs(size.height - targetHeight); 284 } 285 } 286 287 // Cannot find the one match the aspect ratio, ignore the requirement 288 if (optimalSize == null) { 289 minDiff = Double.MAX_VALUE; 290 for (Size size : sizes) { 291 if (Math.abs(size.height - targetHeight) < minDiff) { 292 optimalSize = size; 293 minDiff = Math.abs(size.height - targetHeight); 294 } 295 } 296 } 297 return optimalSize; 298 } 299 surfaceChanged(SurfaceHolder holder, int format, int w, int h)300 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 301 // Now that the size is known, set up the camera parameters and begin 302 // the preview. 303 Camera.Parameters parameters = mCamera.getParameters(); 304 parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); 305 requestLayout(); 306 307 mCamera.setParameters(parameters); 308 mCamera.startPreview(); 309 } 310 311 } 312