1 /*
2  * Copyright (C) 2016 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.sixdof.Activities;
17 
18 import com.android.cts.verifier.R;
19 import com.android.cts.verifier.sensors.sixdof.Activities.StartActivity.ResultCode;
20 import com.android.cts.verifier.sensors.sixdof.Fragments.AccuracyFragment;
21 import com.android.cts.verifier.sensors.sixdof.Fragments.ComplexMovementFragment;
22 import com.android.cts.verifier.sensors.sixdof.Fragments.DataFragment;
23 import com.android.cts.verifier.sensors.sixdof.Fragments.PhaseStartFragment;
24 import com.android.cts.verifier.sensors.sixdof.Fragments.RobustnessFragment;
25 import com.android.cts.verifier.sensors.sixdof.Interfaces.AccuracyListener;
26 import com.android.cts.verifier.sensors.sixdof.Interfaces.BaseUiListener;
27 import com.android.cts.verifier.sensors.sixdof.Interfaces.ComplexMovementListener;
28 import com.android.cts.verifier.sensors.sixdof.Interfaces.RobustnessListener;
29 import com.android.cts.verifier.sensors.sixdof.Utils.ReportExporter;
30 import com.android.cts.verifier.sensors.sixdof.Utils.TestReport;
31 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointAreaCoveredException;
32 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointDistanceException;
33 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointRingNotEnteredException;
34 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointStartPointException;
35 import com.android.cts.verifier.sensors.sixdof.Utils.Manager.Lap;
36 import com.android.cts.verifier.sensors.sixdof.Utils.Path.PathUtilityClasses.Ring;
37 import com.android.cts.verifier.sensors.sixdof.Utils.Path.PathUtilityClasses.RotationData;
38 import com.android.cts.verifier.sensors.sixdof.Utils.Path.PathUtilityClasses.Waypoint;
39 import com.android.cts.verifier.sensors.sixdof.Utils.PoseProvider.PoseProvider;
40 import com.android.cts.verifier.sensors.sixdof.Utils.ResultObjects.ResultObject;
41 
42 import android.content.Intent;
43 import android.content.pm.ActivityInfo;
44 import android.content.res.Configuration;
45 import android.content.res.Resources;
46 import android.os.Bundle;
47 import android.app.Fragment;
48 import android.app.FragmentManager;
49 import android.app.FragmentTransaction;
50 import android.app.AlertDialog;
51 import android.app.Activity;
52 import android.util.Log;
53 import android.view.Display;
54 import android.view.Menu;
55 import android.view.MenuItem;
56 import android.view.Surface;
57 
58 import java.io.IOException;
59 import java.util.ArrayList;
60 
61 /**
62  * Main Activity for 6DOF tests Handles calls between UI fragments and the Data fragment. The
63  * controller in the MVC structure.
64  */
65 public class TestActivity extends Activity implements BaseUiListener, AccuracyListener,
66         RobustnessListener, ComplexMovementListener {
67 
68     private static final String TAG = "TestActivity";
69     private static final String TAG_DATA_FRAGMENT = "data_fragment";
70     public static final String EXTRA_RESULT_ID = "extraResult";
71     public static final String EXTRA_REPORT = "extraReport";
72     public static final String EXTRA_ON_RESTART = "6dof_verifier_restart";
73     public static final Object POSE_LOCK = new Object();
74 
75     private DataFragment mDataFragment;
76 
77     private BaseUiListener mUiListener;
78     private AccuracyListener mAccuracyListener;
79     private RobustnessListener mRobustnessListener;
80     private ComplexMovementListener mComplexMovementListener;
81 
82     private CTSTest mCurrentTest = CTSTest.ACCURACY;
83 
84     private boolean mHasBeenPaused = false;
85 
86     public enum CTSTest {
87         ACCURACY,
88         ROBUSTNESS,
89         COMPLEX_MOVEMENT
90     }
91 
92     /**
93      * Initialises camera preview, looks for a retained data fragment if we have one and adds UI
94      * fragment.
95      */
96     @Override
onCreate(Bundle savedInstanceState)97     protected void onCreate(Bundle savedInstanceState) {
98         super.onCreate(savedInstanceState);
99 
100         // If we are restarting, kill the test as data is invalid.
101         if (savedInstanceState != null) {
102             if (savedInstanceState.getBoolean(EXTRA_ON_RESTART)) {
103                 Intent intent = this.getIntent();
104                 intent.putExtra(EXTRA_RESULT_ID, ResultCode.FAILED_PAUSE_AND_RESUME);
105                 this.setResult(RESULT_OK, intent);
106                 finish();
107             }
108         }
109 
110         setContentView(R.layout.activity_cts);
111 
112         // Add the first instructions fragment.
113         Fragment fragment = PhaseStartFragment.newInstance(CTSTest.ACCURACY);
114         FragmentManager fragmentManager = getFragmentManager();
115         FragmentTransaction transaction = fragmentManager.beginTransaction();
116         transaction.replace(R.id.contentFragment, fragment);
117         transaction.commit();
118 
119         mDataFragment = new DataFragment();
120         fragmentManager.beginTransaction().add(mDataFragment, TAG_DATA_FRAGMENT).commit();
121 
122         // Lock the screen to its current rotation
123         lockRotation();
124     }
125 
126     /**
127      * Lock the orientation of the device in its current state.
128      */
lockRotation()129     private void lockRotation() {
130         final Display display = getWindowManager().getDefaultDisplay();
131         int naturalOrientation = Configuration.ORIENTATION_LANDSCAPE;
132         int configOrientation = getResources().getConfiguration().orientation;
133         switch (display.getRotation()) {
134             case Surface.ROTATION_0:
135             case Surface.ROTATION_180:
136                 // We are currently in the same basic orientation as the natural orientation
137                 naturalOrientation = configOrientation;
138                 break;
139             case Surface.ROTATION_90:
140             case Surface.ROTATION_270:
141                 // We are currently in the other basic orientation to the natural orientation
142                 naturalOrientation = (configOrientation == Configuration.ORIENTATION_LANDSCAPE) ?
143                         Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
144                 break;
145         }
146 
147         int[] orientationMap = {
148                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
149                 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
150                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
151                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
152         };
153         // Since the map starts at portrait, we need to offset if this device's natural orientation
154         // is landscape.
155         int indexOffset = 0;
156         if (naturalOrientation == Configuration.ORIENTATION_LANDSCAPE) {
157             indexOffset = 1;
158         }
159 
160         // The map assumes default rotation. Check for reverse rotation and correct map if required
161         try {
162             if (getResources().getBoolean(getResources().getSystem().getIdentifier(
163                     "config_reverseDefaultRotation", "bool", "android"))) {
164                 orientationMap[0] = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
165                 orientationMap[2] = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
166             }
167         } catch (Resources.NotFoundException e) {
168             // If resource is not found, assume default rotation and continue
169             Log.d(TAG, "Cannot determine device rotation direction, assuming default");
170         }
171 
172         setRequestedOrientation(orientationMap[(display.getRotation() + indexOffset) % 4]);
173     }
174 
175     @Override
onResume()176     public void onResume() {
177         super.onResume();
178 
179         // 6DoF is reset after a recreation of activity, which invalidates the tests.
180         if (mHasBeenPaused) {
181             Intent intent = this.getIntent();
182             intent.putExtra(EXTRA_RESULT_ID, ResultCode.FAILED_PAUSE_AND_RESUME);
183             this.setResult(RESULT_OK, intent);
184             finish();
185         }
186     }
187 
188     @Override
onPause()189     public void onPause() {
190         super.onPause();
191         mHasBeenPaused = true;
192     }
193 
194     @Override
onCreateOptionsMenu(Menu menu)195     public boolean onCreateOptionsMenu(Menu menu) {
196         // Inflate the menu; this adds items to the action bar if it is present.
197         getMenuInflater().inflate(R.menu.menu_cts, menu);
198         return true;
199     }
200 
201     @Override
onOptionsItemSelected(MenuItem item)202     public boolean onOptionsItemSelected(MenuItem item) {
203         // Handle action bar item clicks here.
204         int id = item.getItemId();
205 
206         switch (id) {
207             case R.id.action_save_results:
208                 saveResults();
209                 return true;
210             case R.id.action_xml:
211                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
212 
213                 try {
214                     builder.setMessage(mDataFragment.getTestReport().getContents())
215                             .setTitle(R.string.results)
216                             .setPositiveButton(R.string.got_it, null);
217                 } catch (IOException e) {
218                     Log.e(TAG, e.toString());
219                 }
220 
221                 AlertDialog dialog = builder.create();
222                 dialog.show();
223                 return true;
224             default:
225                 return super.onOptionsItemSelected(item);
226         }
227     }
228 
saveResults()229     public void saveResults() {
230         try {
231             new ReportExporter(this, getTestReport().getContents()).execute();
232         } catch (IOException e) {
233             Log.e(TAG, "Couldn't create test report.");
234         }
235     }
236 
getTestReport()237     public TestReport getTestReport() {
238         return mDataFragment.getTestReport();
239     }
240 
listenFor6DofData(Fragment listener)241     public void listenFor6DofData(Fragment listener) {
242         mUiListener = (BaseUiListener) listener;
243         switch (mCurrentTest) {
244             case ACCURACY:
245                 mAccuracyListener = (AccuracyListener) listener;
246                 mRobustnessListener = null;
247                 mComplexMovementListener = null;
248                 break;
249             case ROBUSTNESS:
250                 mAccuracyListener = null;
251                 mRobustnessListener = (RobustnessListener) listener;
252                 mComplexMovementListener = null;
253                 break;
254             case COMPLEX_MOVEMENT:
255                 mAccuracyListener = null;
256                 mRobustnessListener = null;
257                 mComplexMovementListener = (ComplexMovementListener) listener;
258                 break;
259             default:
260                 throw new AssertionError("mCurrentTest is a test that doesn't exist!");
261         }
262     }
263 
isPoseProviderReady()264     public boolean isPoseProviderReady() {
265         if (mDataFragment != null) {
266             return mDataFragment.isPoseProviderReady();
267         } else {
268             return false;
269         }
270 
271     }
272 
getUserGeneratedWaypoints(Lap lap)273     public ArrayList<Waypoint> getUserGeneratedWaypoints(Lap lap) {
274         return mDataFragment.getUserGeneratedWaypoints(lap);
275     }
276 
getLap()277     public Lap getLap() {
278         return mDataFragment.getLap();
279     }
280 
getRings()281     public ArrayList<Ring> getRings() {
282         return mDataFragment.getRings();
283     }
284 
285     @Override
onPoseProviderReady()286     public void onPoseProviderReady() {
287         if (mUiListener != null) {
288             mUiListener.onPoseProviderReady();
289         } else {
290             Log.e(TAG, getString(R.string.error_null_fragment));
291         }
292 
293         // Possible for this to be called while switching UI fragments, so mUiListener is null
294         // but we want to start the test anyway.
295         mDataFragment.testStarted();
296     }
297 
298     @Override
onWaypointPlaced()299     public void onWaypointPlaced() {
300         if (mUiListener != null) {
301             mUiListener.onWaypointPlaced();
302         } else {
303             Log.e(TAG, getString(R.string.error_null_fragment));
304         }
305     }
306 
307     @Override
onResult(ResultObject result)308     public void onResult(ResultObject result) {
309         if (mUiListener != null) {
310             mUiListener.onResult(result);
311         } else {
312             Log.e(TAG, getString(R.string.error_null_fragment));
313         }
314     }
315 
316     @Override
onReset()317     public void onReset() {
318         if (mAccuracyListener != null) {
319             if (mCurrentTest == CTSTest.ACCURACY) {
320                 mAccuracyListener.onReset();
321             } else {
322                 throw new RuntimeException("We are in the wrong test for this listener to be called.");
323             }
324         } else {
325             Log.e(TAG, getString(R.string.error_null_fragment));
326         }
327     }
328 
329     @Override
lap1Complete()330     public void lap1Complete() {
331         if (mAccuracyListener != null) {
332             mAccuracyListener.lap1Complete();
333         } else {
334             Log.e(TAG, getString(R.string.error_null_fragment));
335         }
336     }
337 
attemptWaypointPlacement()338     public void attemptWaypointPlacement() throws WaypointAreaCoveredException, WaypointDistanceException, WaypointStartPointException, WaypointRingNotEnteredException {
339         mDataFragment.onWaypointPlacementAttempt();
340     }
341 
undoWaypointPlacement()342     public void undoWaypointPlacement() {
343         if (mDataFragment != null) {
344             mDataFragment.undoWaypointPlacement();
345         } else {
346             Log.e(TAG, getString(R.string.error_retained_fragment_null));
347         }
348     }
349 
readyForLap2()350     public void readyForLap2() {
351         mDataFragment.startTest(CTSTest.ACCURACY);
352     }
353 
getLatestDistanceData()354     public float getLatestDistanceData() {
355         return mDataFragment.getLatestDistanceData();
356     }
357 
getTimeRemaining()358     public float getTimeRemaining() {
359         return mDataFragment.getTimeRemaining();
360     }
361 
getPoseProvider()362     public PoseProvider getPoseProvider() {
363         return mDataFragment.getPoseProvider();
364     }
365 
366     @Override
onNewRotationData(RotationData data)367     public void onNewRotationData(RotationData data) {
368         if (mRobustnessListener != null) {
369             mRobustnessListener.onNewRotationData(data);
370         } else {
371             Log.e(TAG, getString(R.string.error_null_fragment));
372         }
373     }
374 
375     @Override
onRingEntered(Ring ring)376     public void onRingEntered(Ring ring) {
377         if (mComplexMovementListener != null) {
378             mComplexMovementListener.onRingEntered(ring);
379         } else {
380             Log.e(TAG, getString(R.string.error_null_fragment));
381         }
382     }
383 
384     /**
385      * Loads test fragment for a particular phase.
386      *
387      * @param phase test to be started.
388      */
switchToTestFragment(CTSTest phase)389     public void switchToTestFragment(CTSTest phase) {
390         Log.d(TAG, "switchToTestFragment");
391         Fragment fragment;
392 
393         switch (phase) {
394             case ACCURACY:
395                 fragment = AccuracyFragment.newInstance();
396                 break;
397             case ROBUSTNESS:
398                 fragment = RobustnessFragment.newInstance();
399                 break;
400             case COMPLEX_MOVEMENT:
401                 fragment = ComplexMovementFragment.newInstance(); //Complex Motion
402                 break;
403             default:
404                 throw new AssertionError("Trying to start a test that doesn't exist!");
405         }
406         FragmentManager fm = getFragmentManager();
407         FragmentTransaction transaction = fm.beginTransaction();
408         transaction.replace(R.id.contentFragment, fragment);
409         transaction.commit();
410     }
411 
412     /**
413      * Loads start instruction fragment for a particular test.
414      *
415      * @param phase test to show instruction screen for.
416      */
switchToStartFragment(CTSTest phase)417     public void switchToStartFragment(CTSTest phase) {
418         Log.e(TAG, "switchToStartFragment");
419         mUiListener = null;
420         mAccuracyListener = null;
421         mRobustnessListener = null;
422         mComplexMovementListener = null;
423 
424         mCurrentTest = phase;
425         mDataFragment.startTest(mCurrentTest);
426         Fragment fragment = PhaseStartFragment.newInstance(phase);
427         FragmentManager fm = getFragmentManager();
428         FragmentTransaction transaction = fm.beginTransaction();
429         transaction.replace(R.id.contentFragment, fragment);
430         transaction.commit();
431     }
432 
433     @Override
onSaveInstanceState(Bundle outState)434     protected void onSaveInstanceState(Bundle outState) {
435         // We are always going to be restarting if this is called.
436         outState.putBoolean(EXTRA_ON_RESTART, true);
437         super.onSaveInstanceState(outState);
438     }
439 
440     @Override
onDestroy()441     protected void onDestroy() {
442         super.onDestroy();
443         onDestroyUi();
444         mUiListener = null;
445         mAccuracyListener = null;
446         mRobustnessListener = null;
447         mComplexMovementListener = null;
448         mDataFragment = null;
449     }
450 
451     @Override
onDestroyUi()452     public void onDestroyUi() {
453         if (mUiListener != null) {
454             mUiListener.onDestroyUi();
455         }
456     }
457 }
458