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.PARTIAL_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 Assert.assertEquals("Roll RMS error", 0.0, mReport.roll_rms_error, 305 Criterion.roll_rms_error); 306 Assert.assertEquals("Roll max error", 0.0, mReport.roll_max_error, 307 Criterion.roll_max_error); 308 return message; 309 } 310 test2PitchAxis()311 public String test2PitchAxis() throws Throwable { 312 313 loadOpenCVSuccessfulOrSkip(); 314 recordSuccessfulOrSkip(); 315 analyzeSuccessfulOrSkip(); 316 317 String message = "Test Pitch Axis Accuracy"; 318 319 Assert.assertEquals("Pitch RMS error", 0.0, mReport.pitch_rms_error, 320 Criterion.pitch_rms_error); 321 Assert.assertEquals("Pitch max error", 0.0, mReport.pitch_max_error, 322 Criterion.pitch_max_error); 323 return message; 324 } 325 test3YawAxis()326 public String test3YawAxis() throws Throwable { 327 328 loadOpenCVSuccessfulOrSkip(); 329 recordSuccessfulOrSkip(); 330 analyzeSuccessfulOrSkip(); 331 332 String message = "Test Yaw Axis Accuracy"; 333 334 Assert.assertEquals("Yaw RMS error", 0.0, mReport.yaw_rms_error, 335 Criterion.yaw_rms_error); 336 Assert.assertEquals("Yaw max error", 0.0, mReport.yaw_max_error, 337 Criterion.yaw_max_error); 338 return message; 339 } 340 loadOpenCVSuccessfulOrSkip()341 private void loadOpenCVSuccessfulOrSkip() throws SensorTestStateNotSupportedException { 342 if (!mOpenCVLoadSuccessful) 343 throw new SensorTestStateNotSupportedException("Skipped due to OpenCV cannot be loaded"); 344 } 345 recordSuccessfulOrSkip()346 private void recordSuccessfulOrSkip() throws SensorTestStateNotSupportedException { 347 if (!mRecordSuccessful) 348 throw new SensorTestStateNotSupportedException("Skipped due to record failure."); 349 } 350 analyzeSuccessfulOrSkip()351 private void analyzeSuccessfulOrSkip() throws SensorTestStateNotSupportedException { 352 if (mReport == null || mReport.error) 353 throw new SensorTestStateNotSupportedException("Skipped due to CV Analysis failure."); 354 } 355 356 /* 357 * This function serves as a proxy as appendText is marked to be deprecated. 358 * When appendText is removed, this function will have a different implementation. 359 * 360 */ showUserMessage(String s)361 private void showUserMessage(String s) { 362 appendText(s); 363 } 364 showDetailedTutorialLink()365 private void showDetailedTutorialLink() { 366 TextView textView = new TextView(this); 367 textView.setText(Html.fromHtml( 368 "Detailed test instructions can be found at " + 369 "<A href=\"http://goo.gl/xTwB4d\">http://goo.gl/xTwB4d</a><br>")); 370 textView.setMovementMethod(LinkMovementMethod.getInstance()); 371 textView.setPadding(10, 0, 0, 0); 372 getTestLogger().logCustomView(textView); 373 } 374 showOpenCVLibaryLicenseDisplayButton()375 private void showOpenCVLibaryLicenseDisplayButton() { 376 Button btnLicense = new Button(this); 377 btnLicense.setText("View OpenCV Library BSD License Agreement"); 378 // Avoid default all cap text on button. 379 btnLicense.setTransformationMethod(null); 380 btnLicense.setLayoutParams(new LayoutParams( 381 ViewGroup.LayoutParams.WRAP_CONTENT, 382 ViewGroup.LayoutParams.WRAP_CONTENT)); 383 384 // load 385 Resources res = getResources(); 386 InputStream rawStream = res.openRawResource(R.raw.opencv_library_license); 387 byte[] byteArray; 388 try { 389 byteArray = new byte[rawStream.available()]; 390 rawStream.read(byteArray); 391 } catch (IOException e) { 392 e.printStackTrace(); 393 byteArray = "Unable to load license text.".getBytes(); 394 } 395 final String licenseText = new String(byteArray); 396 397 btnLicense.setOnClickListener(new OnClickListener() { 398 public void onClick(View v) { 399 AlertDialog dialog = 400 new AlertDialog.Builder(RVCVXCheckTestActivity.this) 401 .setTitle("OpenCV Library BSD License") 402 .setMessage(licenseText) 403 .setPositiveButton("Acknowledged", new DialogInterface.OnClickListener() { 404 public void onClick(DialogInterface dialog, int id) { 405 dialog.dismiss(); 406 } 407 }) 408 .show(); 409 410 TextView textView = (TextView) dialog.findViewById(android.R.id.message); 411 textView.setTextSize(9); 412 } 413 }); 414 getTestLogger().logCustomView(btnLicense); 415 } 416 417 @Override onCreate(Bundle savedInstanceState)418 protected void onCreate(Bundle savedInstanceState) { 419 420 super.onCreate(savedInstanceState); 421 422 // GlSurfaceView is not necessary for this test 423 closeGlSurfaceView(); 424 } 425 426 @Override onPause()427 protected void onPause() { 428 super.onPause(); 429 } 430 431 @Override onResume()432 protected void onResume() { 433 super.onResume(); 434 435 } 436 } 437