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
onCreate(Bundle savedInstanceState)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
onActivityCreated(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)88     public View onCreateView(LayoutInflater inflater, ViewGroup container,
89             Bundle savedInstanceState) {
90         return mPreview;
91     }
92 
93     @Override
onResume()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
onPause()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
onCreateOptionsMenu(Menu menu, MenuInflater inflater)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
onOptionsItemSelected(MenuItem item)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 
Preview(Context context)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 
setCamera(Camera camera)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 
switchCamera(Camera camera)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onLayout(boolean changed, int l, int t, int r, int b)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 
surfaceCreated(SurfaceHolder holder)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 
surfaceDestroyed(SurfaceHolder holder)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 
getOptimalPreviewSize(List<Size> sizes, int w, int h)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 
surfaceChanged(SurfaceHolder holder, int format, int w, int h)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