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