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