1 /*
2  * Copyright (C) 2013 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 package com.android.cts.verifier.sensors;
17 
18 // ----------------------------------------------------------------------
19 
20 import android.content.Context;
21 import android.hardware.Camera;
22 import android.util.AttributeSet;
23 import android.util.Log;
24 import android.view.Surface;
25 import android.view.SurfaceHolder;
26 import android.view.SurfaceView;
27 import android.view.ViewGroup;
28 import android.view.WindowManager;
29 
30 import java.io.IOException;
31 import java.lang.Math;
32 
33 import com.android.cts.verifier.sensors.RVCVRecordActivity;
34 import com.android.cts.verifier.sensors.RVCVRecordActivity.RecordProcedureControllerCallback;
35 
36 /** Camera preview class */
37 public class RVCVCameraPreview extends SurfaceView implements SurfaceHolder.Callback {
38     private static final String TAG = "RVCVCameraPreview";
39     private static final boolean LOCAL_LOGD = true;
40 
41     private Context mContext = null;
42     private SurfaceHolder mHolder;
43     private Camera mCamera;
44     private float mCameraAspectRatio = 0;
45     private int mCameraRotation = 0;
46     private boolean mCheckStartTest = false;
47     private boolean mPreviewStarted = false;
48 
49     private RVCVRecordActivity.RecordProcedureControllerCallback mRecordProcedureControllerCallback;
50 
51     /**
52      * Constructor
53      * @param context Activity context
54      */
RVCVCameraPreview(Context context)55     public RVCVCameraPreview(Context context) {
56         super(context);
57         mContext = context;
58         mCamera = null;
59         initSurface();
60     }
61 
62     /**
63      * Constructor
64      * @param context Activity context
65      * @param attrs
66      */
RVCVCameraPreview(Context context, AttributeSet attrs)67     public RVCVCameraPreview(Context context, AttributeSet attrs) {
68         super(context, attrs);
69         mContext = context;
70     }
71 
init(Camera camera, float aspectRatio, int rotation)72     public void init(Camera camera, float aspectRatio, int rotation)  {
73         this.mCamera = camera;
74         mCameraAspectRatio = aspectRatio;
75         mCameraRotation = rotation;
76         initSurface();
77     }
78 
initSurface()79     private void initSurface() {
80         // Install a SurfaceHolder.Callback so we get notified when the
81         // underlying surface is created and destroyed.
82         mHolder = getHolder();
83         mHolder.addCallback(this);
84 
85         // deprecated
86         // TODO: update this code to match new API level.
87         mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
88     }
89 
90     /**
91      *  SurfaceHolder.Callback
92      *  Surface is created, it is OK to start the camera preview now.
93      */
surfaceCreated(SurfaceHolder holder)94     public void surfaceCreated(SurfaceHolder holder) {
95         // The Surface has been created, now tell the camera where to draw the preview.
96 
97         if (mCamera == null) {
98             // preview camera does not exist
99             return;
100         }
101     }
102     /**
103      *  SurfaceHolder.Callback
104      */
surfaceDestroyed(SurfaceHolder holder)105     public void surfaceDestroyed(SurfaceHolder holder) {
106         // empty. Take care of releasing the Camera preview in your activity.
107     }
108 
109     /**
110      *  SurfaceHolder.Callback
111      *  Restart camera preview if surface changed
112      */
surfaceChanged(SurfaceHolder holder, int format, int w, int h)113     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
114 
115         if (mHolder.getSurface() == null || mCamera == null){
116             // preview surface or camera does not exist
117             return;
118         }
119 
120         int totalRotation = getRequiredRotation();
121         mCamera.setDisplayOrientation(totalRotation);
122 
123         if (adjustLayoutParamsIfNeeded(totalRotation)) {
124             // Wait on next surfaceChanged() call before proceeding
125             Log.d(TAG, "Waiting on surface change before starting preview");
126             return;
127         }
128 
129         if (mPreviewStarted) {
130             Log.w(TAG, "Re-starting camera preview");
131             if (mCheckStartTest && mRecordProcedureControllerCallback != null) {
132                 mRecordProcedureControllerCallback.stopRecordProcedureController();
133             }
134             mCamera.stopPreview();
135             mPreviewStarted = false;
136         }
137         mCheckStartTest = false;
138 
139         try {
140             mCamera.setPreviewDisplay(holder);
141             mCamera.startPreview();
142             mPreviewStarted = true;
143             if (mRecordProcedureControllerCallback != null) {
144                 mCheckStartTest = true;
145                 mRecordProcedureControllerCallback.startRecordProcedureController();
146             }
147         } catch (IOException e) {
148             if (LOCAL_LOGD) Log.d(TAG, "Error when starting camera preview: " + e.getMessage());
149         }
150     }
151 
152     /**
153      * Determine the rotation required to display the camera's preview on the screen as large as
154      * possible. This function combines the device's current rotation from its default orientation
155      * and the rotation of the camera.
156      */
getRequiredRotation()157     private int getRequiredRotation() {
158         WindowManager windowManager =
159                 (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
160         int deviceRotation = 0;
161         if (windowManager != null) {
162             switch (windowManager.getDefaultDisplay().getRotation()) {
163                 case Surface.ROTATION_0:
164                     deviceRotation = 0;
165                     break;
166                 case Surface.ROTATION_90:
167                     deviceRotation = 270;
168                     break;
169                 case Surface.ROTATION_180:
170                     deviceRotation = 180;
171                     break;
172                 case Surface.ROTATION_270:
173                     deviceRotation = 90;
174                     break;
175                 default:
176                     deviceRotation = 0;
177                     break;
178             }
179         } else {
180             Log.w(TAG, "Unable to get device rotation, preview may be skewed.");
181         }
182 
183         return (mCameraRotation + deviceRotation) % 360;
184     }
185 
186     /**
187      * Resize the layout to more closely match the desired aspect ratio, if necessary.
188      *
189      * @return true if we updated the layout params, false if the params look good
190      */
adjustLayoutParamsIfNeeded(int totalRotation)191     private boolean adjustLayoutParamsIfNeeded(int totalRotation) {
192         // Determine the maximum size layout that maintains the camera's preview aspect ratio
193         float cameraAspect = mCameraAspectRatio;
194 
195         // Check the camera and device rotation and invert the aspect ratio if the device is not
196         // rotated at 0 or 180 degrees.
197         if (totalRotation % 180 != 0) {
198             // The device is rotated, so the screen should be the inverse of the aspect ratio
199             cameraAspect = 1.0f / mCameraAspectRatio;
200         }
201 
202         // Only adjust if there is at least 1% error between the aspects
203         ViewGroup.LayoutParams layoutParams = getLayoutParams();
204         int curWidth = getWidth();
205         int curHeight = getHeight();
206         float curAspect = (float)curWidth / (float)curHeight;
207         float aspectDelta = Math.abs(cameraAspect - curAspect);
208         if ((aspectDelta / cameraAspect) >= 0.01) {
209             if (cameraAspect > curAspect) {
210                 // Camera preview is wider than the current layout. Need to shorten the current layout
211                 layoutParams.width = curWidth;
212                 layoutParams.height = (int)(curWidth / cameraAspect);
213             } else {
214                 // Camera preview taller than the current layout. Need to narrow the current layout
215                 layoutParams.width = (int)(curHeight * cameraAspect);
216                 layoutParams.height = curHeight;
217             }
218 
219             if (layoutParams.height != curHeight || layoutParams.width != curWidth) {
220                 Log.d(TAG, String.format("Layout (%d, %d) -> (%d, %d)", curWidth, curHeight,
221                         layoutParams.width, layoutParams.height));
222                 setLayoutParams(layoutParams);
223                 return true;
224             }
225         }
226         return false;
227     }
228 
setRecordProcedureControllerCallback( RVCVRecordActivity.RecordProcedureControllerCallback callback)229     public void setRecordProcedureControllerCallback(
230             RVCVRecordActivity.RecordProcedureControllerCallback callback) {
231         mRecordProcedureControllerCallback = callback;
232     }
233 }
234