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