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