1 /* 2 * Copyright (C) 2014 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.android.cts.verifier.sensors; 18 19 import android.app.AlertDialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.res.Resources; 24 import android.hardware.cts.helpers.SensorTestStateNotSupportedException; 25 import android.os.Bundle; 26 import android.os.PowerManager; 27 import android.text.method.LinkMovementMethod; 28 import android.text.Html; 29 import android.view.View; 30 import android.view.View.OnClickListener; 31 import android.view.ViewGroup; 32 import android.view.ViewGroup.LayoutParams; 33 import android.widget.Button; 34 import android.widget.TextView; 35 36 import com.android.compatibility.common.util.ReportLog; 37 import com.android.compatibility.common.util.ResultType; 38 import com.android.compatibility.common.util.ResultUnit; 39 import com.android.cts.verifier.R; 40 import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity; 41 import com.android.cts.verifier.sensors.helpers.OpenCVLibrary; 42 43 import junit.framework.Assert; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.util.concurrent.CountDownLatch; 47 48 /** 49 * This test (Rotation Vector - Computer Vision Cross Check, or RXCVXCheck for short) verifies that 50 * mobile device can detect the orientation of itself in a relatively accurate manner. 51 * 52 * Currently only ROTATION_VECTOR sensor is used. 53 * 54 */ 55 public class RVCVXCheckTestActivity 56 extends SensorCtsVerifierTestActivity { RVCVXCheckTestActivity()57 public RVCVXCheckTestActivity() { 58 super(RVCVXCheckTestActivity.class); 59 } 60 61 CountDownLatch mRecordActivityFinishedSignal = null; 62 63 private static final int REQ_CODE_TXCVRECORD = 0x012345678; 64 private static final boolean TEST_USING_DEBUGGING_DATA = false; 65 private static final String PATH_DEBUGGING_DATA = "/sdcard/RXCVRecData/150313-014443/"; 66 67 private String mRecPath; 68 69 RVCVXCheckAnalyzer.AnalyzeReport mReport = null; 70 71 private boolean mRecordSuccessful = false; 72 private boolean mOpenCVLoadSuccessful = false; 73 74 private static class Criterion { 75 public static final float roll_rms_error = 0.15f; 76 public static final float pitch_rms_error = 0.15f; 77 public static final float yaw_rms_error = 0.25f; 78 79 public static final float roll_max_error = 0.35f; 80 public static final float pitch_max_error = 0.35f; 81 public static final float yaw_max_error = 0.5f; 82 83 public static final float sensor_period_stdev = 0.25e-3f; 84 }; 85 86 87 /** 88 * The activity setup collects all the required data for test cases. 89 * This approach allows to test all sensors at once. 90 */ 91 @Override activitySetUp()92 protected void activitySetUp() throws InterruptedException { 93 94 mRecPath = ""; 95 96 // Test result is fail by default. 97 getReportLog().setSummary( 98 "Initialize failed", 0, ResultType.NEUTRAL, ResultUnit.NONE); 99 100 showDetailedTutorialLink(); 101 102 showUserMessage("Loading OpenCV Library..."); 103 104 if (!OpenCVLibrary.load(this, 105 false /*allowLocal*/, true/*allowPackage*/, false/*allowInstall*/)) { 106 // cannot load opencv library 107 showUserMessage("Cannot load OpenCV library! Please follow instruction and install " + 108 "OpenCV Manager 3.0.0 and start this test again."); 109 waitForUserToContinue(); 110 clearText(); 111 return; 112 } 113 showUserMessage("OpenCV Library Successfully Loaded."); 114 showOpenCVLibaryLicenseDisplayButton(); 115 116 mOpenCVLoadSuccessful = true; 117 118 if (TEST_USING_DEBUGGING_DATA) { 119 mRecPath = PATH_DEBUGGING_DATA; 120 121 // assume the data is there already 122 mRecordSuccessful = true; 123 } else { 124 showUserMessage("Take the test as instructed below:\n" + 125 "1. Print out the test pattern and place it on a "+ 126 "horizontal surface.\n" + 127 "2. Start the test and align the yellow square on the screen "+ 128 "roughly to the yellow sqaure.\n" + 129 "3. Follow the prompt to orbit the device around the test " + 130 "pattern while aiming the field of view at the test pattern" + 131 "at the same time.\n" + 132 "4. Wait patiently for the analysis to finish."); 133 134 waitForUserToContinue(); 135 136 // prepare sync signal 137 mRecordActivityFinishedSignal = new CountDownLatch(1); 138 139 // record both sensor and camera 140 Intent intent = new Intent(this, RVCVRecordActivity.class); 141 startActivityForResult(intent, REQ_CODE_TXCVRECORD); 142 143 // wait for record finish 144 mRecordActivityFinishedSignal.await(); 145 146 if ("".equals(mRecPath)) { 147 showUserMessage("Recording failed or exited prematurely."); 148 waitForUserToContinue(); 149 } else { 150 showUserMessage("Recording is done!"); 151 showUserMessage("Result are in path: " + mRecPath); 152 mRecordSuccessful = true; 153 } 154 } 155 156 157 if (!mRecordSuccessful) { 158 getReportLog().setSummary( 159 "Record failed", 0, ResultType.NEUTRAL, ResultUnit.NONE); 160 } else { 161 showUserMessage("Please wait for the analysis ... \n"+ 162 "It may take a few minutes, you will be noted when "+ 163 "its finished by sound and vibration. "); 164 165 // Analysis of recorded video and sensor data using RVCXAnalyzer 166 RVCVXCheckAnalyzer analyzer = new RVCVXCheckAnalyzer(mRecPath); 167 168 // acquire a partial wake lock just in case CPU fall asleep 169 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 170 PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, 171 "RVCVXCheckAnalyzer"); 172 173 wl.acquire(); 174 mReport = analyzer.processDataSet(); 175 wl.release(); 176 177 playSound(); 178 vibrate(500); 179 180 if (mReport == null) { 181 showUserMessage("Analysis failed due to unknown reason!"); 182 } else { 183 if (mReport.error) { 184 getReportLog().setSummary("Analysis failed: "+mReport.reason, 0, 185 ResultType.NEUTRAL, ResultUnit.NONE); 186 187 showUserMessage("Analysis failed: " + mReport.reason); 188 } else { 189 getReportLog().setSummary( 190 "Analysis succeed", 1, ResultType.NEUTRAL, ResultUnit.NONE); 191 192 getReportLog().addValue("Roll error RMS", mReport.roll_rms_error, 193 ResultType.LOWER_BETTER, ResultUnit.RADIAN); 194 getReportLog().addValue("Pitch error RMS", mReport.pitch_rms_error, 195 ResultType.LOWER_BETTER, ResultUnit.RADIAN); 196 getReportLog().addValue("Yaw error RMS", mReport.yaw_rms_error, 197 ResultType.LOWER_BETTER, ResultUnit.RADIAN); 198 199 getReportLog().addValue("Roll error MAX", mReport.roll_max_error, 200 ResultType.LOWER_BETTER, ResultUnit.RADIAN); 201 getReportLog().addValue("Pitch error MAX", mReport.pitch_max_error, 202 ResultType.LOWER_BETTER, ResultUnit.RADIAN); 203 getReportLog().addValue("Yaw error MAX", mReport.yaw_max_error, 204 ResultType.LOWER_BETTER, ResultUnit.RADIAN); 205 206 getReportLog().addValue("Number of frames", mReport.n_of_frame, 207 ResultType.NEUTRAL, ResultUnit.COUNT); 208 getReportLog().addValue("Number of valid frames", mReport.n_of_valid_frame, 209 ResultType.NEUTRAL, ResultUnit.COUNT); 210 211 getReportLog().addValue("Sensor period mean", mReport.sensor_period_avg*1000, 212 ResultType.NEUTRAL, ResultUnit.MS); 213 getReportLog().addValue("Sensor period stdev", mReport.sensor_period_stdev*1000, 214 ResultType.NEUTRAL, ResultUnit.MS); 215 getReportLog().addValue("Time offset", mReport.optimal_delta_t*1000, 216 ResultType.NEUTRAL, ResultUnit.MS); 217 getReportLog().addValue("Yaw offset", mReport.yaw_offset, 218 ResultType.NEUTRAL, ResultUnit.RADIAN); 219 220 showUserMessage(String.format("Analysis finished!\n" + 221 "Roll error (Rms, max) = %4.3f, %4.3f rad\n" + 222 "Pitch error (Rms, max) = %4.3f, %4.3f rad\n" + 223 "Yaw error (Rms, max) = %4.3f, %4.3f rad\n" + 224 "N of Frame (valid, total) = %d, %d\n" + 225 "Sensor period (mean, stdev) = %4.3f, %4.3f ms\n" + 226 "Time offset: %4.3f s \n" + 227 "Yaw offset: %4.3f rad \n\n", 228 mReport.roll_rms_error, mReport.roll_max_error, 229 mReport.pitch_rms_error, mReport.pitch_max_error, 230 mReport.yaw_rms_error, mReport.yaw_max_error, 231 mReport.n_of_valid_frame, mReport.n_of_frame, 232 mReport.sensor_period_avg * 1000.0, mReport.sensor_period_stdev*1000.0, 233 mReport.optimal_delta_t, mReport.yaw_offset)); 234 showUserMessage("Please click next after details reviewed."); 235 waitForUserToContinue(); 236 } 237 } 238 } 239 clearText(); 240 } 241 242 /** 243 Receiving the results from the RVCVRecordActivity, which is a patch where the recorded 244 video and sensor data is stored. 245 */ 246 @Override onActivityResult(int requestCode, int resultCode, Intent data)247 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 248 // Check which request we're responding to 249 if (requestCode == REQ_CODE_TXCVRECORD) { 250 // Make sure the request was successful 251 252 if (resultCode == RESULT_OK) { 253 mRecPath = data.getData().getPath(); 254 } 255 256 // notify it is finished 257 mRecordActivityFinishedSignal.countDown(); 258 } 259 super.onActivityResult(requestCode, resultCode, data); 260 } 261 262 /** 263 * Test cases. 264 * 265 * Test cases is numbered to make sure they are executed in the right order. 266 */ 267 test00OpenCV()268 public String test00OpenCV() throws Throwable { 269 270 String message = "OpenCV is loaded"; 271 Assert.assertTrue("OpenCV library cannot be loaded. If OpenCV Manager is just installed, " + 272 "please restart this test.", mOpenCVLoadSuccessful); 273 return message; 274 } 275 276 test01Recording()277 public String test01Recording() throws Throwable { 278 279 loadOpenCVSuccessfulOrSkip(); 280 281 String message = "Record is successful."; 282 Assert.assertTrue("Record is not successful.", mRecordSuccessful); 283 return message; 284 } 285 test02Analysis()286 public String test02Analysis() throws Throwable { 287 288 loadOpenCVSuccessfulOrSkip(); 289 recordSuccessfulOrSkip(); 290 291 String message = "Analysis result: " + mReport.reason; 292 Assert.assertTrue(message, (mReport!=null && !mReport.error)); 293 return message; 294 } 295 test1RollAxis()296 public String test1RollAxis() throws Throwable { 297 298 loadOpenCVSuccessfulOrSkip(); 299 recordSuccessfulOrSkip(); 300 analyzeSuccessfulOrSkip(); 301 302 String message = "Test Roll Axis Accuracy"; 303 304 assertLessThan("Roll RMS error", mReport.roll_rms_error, Criterion.roll_rms_error); 305 assertLessThan("Roll max error", mReport.roll_max_error, Criterion.roll_max_error); 306 return message; 307 } 308 test2PitchAxis()309 public String test2PitchAxis() throws Throwable { 310 311 loadOpenCVSuccessfulOrSkip(); 312 recordSuccessfulOrSkip(); 313 analyzeSuccessfulOrSkip(); 314 315 String message = "Test Pitch Axis Accuracy"; 316 317 assertLessThan("Pitch RMS error", mReport.pitch_rms_error, Criterion.pitch_rms_error); 318 assertLessThan("Pitch max error", mReport.pitch_max_error, Criterion.pitch_max_error); 319 return message; 320 } 321 test3YawAxis()322 public String test3YawAxis() throws Throwable { 323 324 loadOpenCVSuccessfulOrSkip(); 325 recordSuccessfulOrSkip(); 326 analyzeSuccessfulOrSkip(); 327 328 String message = "Test Yaw Axis Accuracy"; 329 330 assertLessThan("Yaw RMS error", mReport.yaw_rms_error, Criterion.yaw_rms_error); 331 assertLessThan("Yaw max error", mReport.yaw_max_error, Criterion.yaw_max_error); 332 return message; 333 } 334 assertLessThan(String message, double lhs, double rhs)335 private void assertLessThan(String message, double lhs, double rhs) { 336 Assert.assertTrue(String.format("%s - expected %.4f < %.4f", message, lhs, rhs), 337 lhs < rhs); 338 } 339 340 private void loadOpenCVSuccessfulOrSkip() throws SensorTestStateNotSupportedException { 341 if (!mOpenCVLoadSuccessful) 342 throw new SensorTestStateNotSupportedException("Skipped due to OpenCV cannot be loaded"); 343 } 344 345 private void recordSuccessfulOrSkip() throws SensorTestStateNotSupportedException { 346 if (!mRecordSuccessful) 347 throw new SensorTestStateNotSupportedException("Skipped due to record failure."); 348 } 349 350 private void analyzeSuccessfulOrSkip() throws SensorTestStateNotSupportedException { 351 if (mReport == null || mReport.error) 352 throw new SensorTestStateNotSupportedException("Skipped due to CV Analysis failure."); 353 } 354 355 /* 356 * This function serves as a proxy as appendText is marked to be deprecated. 357 * When appendText is removed, this function will have a different implementation. 358 * 359 */ 360 private void showUserMessage(String s) { 361 appendText(s); 362 } 363 364 private void showDetailedTutorialLink() { 365 TextView textView = new TextView(this); 366 textView.setText(Html.fromHtml( 367 "Detailed test instructions can be found at " + 368 "<A href=\"http://goo.gl/xTwB4d\">http://goo.gl/xTwB4d</a><br>")); 369 textView.setMovementMethod(LinkMovementMethod.getInstance()); 370 textView.setPadding(10, 0, 0, 0); 371 getTestLogger().logCustomView(textView); 372 } 373 374 private void showOpenCVLibaryLicenseDisplayButton() { 375 Button btnLicense = new Button(this); 376 btnLicense.setText("View OpenCV Library BSD License Agreement"); 377 // Avoid default all cap text on button. 378 btnLicense.setTransformationMethod(null); 379 btnLicense.setLayoutParams(new LayoutParams( 380 ViewGroup.LayoutParams.WRAP_CONTENT, 381 ViewGroup.LayoutParams.WRAP_CONTENT)); 382 383 // load 384 Resources res = getResources(); 385 InputStream rawStream = res.openRawResource(R.raw.opencv_library_license); 386 byte[] byteArray; 387 try { 388 byteArray = new byte[rawStream.available()]; 389 rawStream.read(byteArray); 390 } catch (IOException e) { 391 e.printStackTrace(); 392 byteArray = "Unable to load license text.".getBytes(); 393 } 394 final String licenseText = new String(byteArray); 395 396 btnLicense.setOnClickListener(new OnClickListener() { 397 public void onClick(View v) { 398 AlertDialog dialog = 399 new AlertDialog.Builder(RVCVXCheckTestActivity.this) 400 .setTitle("OpenCV Library BSD License") 401 .setMessage(licenseText) 402 .setPositiveButton("Acknowledged", new DialogInterface.OnClickListener() { 403 public void onClick(DialogInterface dialog, int id) { 404 dialog.dismiss(); 405 } 406 }) 407 .show(); 408 409 TextView textView = (TextView) dialog.findViewById(android.R.id.message); 410 textView.setTextSize(9); 411 } 412 }); 413 getTestLogger().logCustomView(btnLicense); 414 } 415 416 @Override 417 protected void onCreate(Bundle savedInstanceState) { 418 419 super.onCreate(savedInstanceState); 420 421 // GlSurfaceView is not necessary for this test 422 closeGlSurfaceView(); 423 } 424 425 @Override 426 protected void onPause() { 427 super.onPause(); 428 } 429 430 @Override 431 protected void onResume() { 432 super.onResume(); 433 434 } 435 } 436