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 android.app.Activity;
19 import android.app.AlertDialog;
20 import android.app.Fragment;
21 import android.app.FragmentManager;
22 import android.app.FragmentTransaction;
23 import android.content.Intent;
24 import android.content.pm.ActivityInfo;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.view.Display;
30 import android.view.Menu;
31 import android.view.MenuItem;
32 import android.view.Surface;
33 
34 import com.android.cts.verifier.R;
35 import com.android.cts.verifier.sensors.sixdof.Activities.StartActivity.ResultCode;
36 import com.android.cts.verifier.sensors.sixdof.Fragments.AccuracyFragment;
37 import com.android.cts.verifier.sensors.sixdof.Fragments.ComplexMovementFragment;
38 import com.android.cts.verifier.sensors.sixdof.Fragments.DataFragment;
39 import com.android.cts.verifier.sensors.sixdof.Fragments.PhaseStartFragment;
40 import com.android.cts.verifier.sensors.sixdof.Fragments.RobustnessFragment;
41 import com.android.cts.verifier.sensors.sixdof.Interfaces.AccuracyListener;
42 import com.android.cts.verifier.sensors.sixdof.Interfaces.BaseUiListener;
43 import com.android.cts.verifier.sensors.sixdof.Interfaces.ComplexMovementListener;
44 import com.android.cts.verifier.sensors.sixdof.Interfaces.RobustnessListener;
45 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointAreaCoveredException;
46 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointDistanceException;
47 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointRingNotEnteredException;
48 import com.android.cts.verifier.sensors.sixdof.Utils.Exceptions.WaypointStartPointException;
49 import com.android.cts.verifier.sensors.sixdof.Utils.Manager.Lap;
50 import com.android.cts.verifier.sensors.sixdof.Utils.Path.PathUtilityClasses.Ring;
51 import com.android.cts.verifier.sensors.sixdof.Utils.Path.PathUtilityClasses.RotationData;
52 import com.android.cts.verifier.sensors.sixdof.Utils.Path.PathUtilityClasses.Waypoint;
53 import com.android.cts.verifier.sensors.sixdof.Utils.PoseProvider.PoseProvider;
54 import com.android.cts.verifier.sensors.sixdof.Utils.ReportExporter;
55 import com.android.cts.verifier.sensors.sixdof.Utils.ResultObjects.ResultObject;
56 import com.android.cts.verifier.sensors.sixdof.Utils.TestReport;
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         if (id == R.id.action_save_results) {
207             saveResults();
208             return true;
209         } else if (id == R.id.action_xml) {
210             AlertDialog.Builder builder = new AlertDialog.Builder(this);
211 
212             try {
213                 builder.setMessage(mDataFragment.getTestReport().getContents())
214                         .setTitle(R.string.results)
215                         .setPositiveButton(R.string.got_it, null);
216             } catch (IOException e) {
217                 Log.e(TAG, e.toString());
218             }
219 
220             AlertDialog dialog = builder.create();
221             dialog.show();
222             return true;
223         }
224         return super.onOptionsItemSelected(item);
225     }
226 
saveResults()227     public void saveResults() {
228         try {
229             new ReportExporter(this, getTestReport().getContents()).execute();
230         } catch (IOException e) {
231             Log.e(TAG, "Couldn't create test report.");
232         }
233     }
234 
getTestReport()235     public TestReport getTestReport() {
236         return mDataFragment.getTestReport();
237     }
238 
listenFor6DofData(Fragment listener)239     public void listenFor6DofData(Fragment listener) {
240         mUiListener = (BaseUiListener) listener;
241         switch (mCurrentTest) {
242             case ACCURACY:
243                 mAccuracyListener = (AccuracyListener) listener;
244                 mRobustnessListener = null;
245                 mComplexMovementListener = null;
246                 break;
247             case ROBUSTNESS:
248                 mAccuracyListener = null;
249                 mRobustnessListener = (RobustnessListener) listener;
250                 mComplexMovementListener = null;
251                 break;
252             case COMPLEX_MOVEMENT:
253                 mAccuracyListener = null;
254                 mRobustnessListener = null;
255                 mComplexMovementListener = (ComplexMovementListener) listener;
256                 break;
257             default:
258                 throw new AssertionError("mCurrentTest is a test that doesn't exist!");
259         }
260     }
261 
isPoseProviderReady()262     public boolean isPoseProviderReady() {
263         if (mDataFragment != null) {
264             return mDataFragment.isPoseProviderReady();
265         } else {
266             return false;
267         }
268 
269     }
270 
getUserGeneratedWaypoints(Lap lap)271     public ArrayList<Waypoint> getUserGeneratedWaypoints(Lap lap) {
272         return mDataFragment.getUserGeneratedWaypoints(lap);
273     }
274 
getLap()275     public Lap getLap() {
276         return mDataFragment.getLap();
277     }
278 
getRings()279     public ArrayList<Ring> getRings() {
280         return mDataFragment.getRings();
281     }
282 
283     @Override
onPoseProviderReady()284     public void onPoseProviderReady() {
285         if (mUiListener != null) {
286             mUiListener.onPoseProviderReady();
287         } else {
288             Log.e(TAG, getString(R.string.error_null_fragment));
289         }
290 
291         // Possible for this to be called while switching UI fragments, so mUiListener is null
292         // but we want to start the test anyway.
293         mDataFragment.testStarted();
294     }
295 
296     @Override
onWaypointPlaced()297     public void onWaypointPlaced() {
298         if (mUiListener != null) {
299             mUiListener.onWaypointPlaced();
300         } else {
301             Log.e(TAG, getString(R.string.error_null_fragment));
302         }
303     }
304 
305     @Override
onResult(ResultObject result)306     public void onResult(ResultObject result) {
307         if (mUiListener != null) {
308             mUiListener.onResult(result);
309         } else {
310             Log.e(TAG, getString(R.string.error_null_fragment));
311         }
312     }
313 
314     @Override
onReset()315     public void onReset() {
316         if (mAccuracyListener != null) {
317             if (mCurrentTest == CTSTest.ACCURACY) {
318                 mAccuracyListener.onReset();
319             } else {
320                 throw new RuntimeException("We are in the wrong test for this listener to be called.");
321             }
322         } else {
323             Log.e(TAG, getString(R.string.error_null_fragment));
324         }
325     }
326 
327     @Override
lap1Complete()328     public void lap1Complete() {
329         if (mAccuracyListener != null) {
330             mAccuracyListener.lap1Complete();
331         } else {
332             Log.e(TAG, getString(R.string.error_null_fragment));
333         }
334     }
335 
attemptWaypointPlacement()336     public void attemptWaypointPlacement() throws WaypointAreaCoveredException, WaypointDistanceException, WaypointStartPointException, WaypointRingNotEnteredException {
337         mDataFragment.onWaypointPlacementAttempt();
338     }
339 
undoWaypointPlacement()340     public void undoWaypointPlacement() {
341         if (mDataFragment != null) {
342             mDataFragment.undoWaypointPlacement();
343         } else {
344             Log.e(TAG, getString(R.string.error_retained_fragment_null));
345         }
346     }
347 
readyForLap2()348     public void readyForLap2() {
349         mDataFragment.startTest(CTSTest.ACCURACY);
350     }
351 
getLatestDistanceData()352     public float getLatestDistanceData() {
353         return mDataFragment.getLatestDistanceData();
354     }
355 
getTimeRemaining()356     public float getTimeRemaining() {
357         return mDataFragment.getTimeRemaining();
358     }
359 
getPoseProvider()360     public PoseProvider getPoseProvider() {
361         return mDataFragment.getPoseProvider();
362     }
363 
364     @Override
onNewRotationData(RotationData data)365     public void onNewRotationData(RotationData data) {
366         if (mRobustnessListener != null) {
367             mRobustnessListener.onNewRotationData(data);
368         } else {
369             Log.e(TAG, getString(R.string.error_null_fragment));
370         }
371     }
372 
373     @Override
onRingEntered(Ring ring)374     public void onRingEntered(Ring ring) {
375         if (mComplexMovementListener != null) {
376             mComplexMovementListener.onRingEntered(ring);
377         } else {
378             Log.e(TAG, getString(R.string.error_null_fragment));
379         }
380     }
381 
382     /**
383      * Loads test fragment for a particular phase.
384      *
385      * @param phase test to be started.
386      */
switchToTestFragment(CTSTest phase)387     public void switchToTestFragment(CTSTest phase) {
388         Log.d(TAG, "switchToTestFragment");
389         Fragment fragment;
390 
391         switch (phase) {
392             case ACCURACY:
393                 fragment = AccuracyFragment.newInstance();
394                 break;
395             case ROBUSTNESS:
396                 fragment = RobustnessFragment.newInstance();
397                 break;
398             case COMPLEX_MOVEMENT:
399                 fragment = ComplexMovementFragment.newInstance(); //Complex Motion
400                 break;
401             default:
402                 throw new AssertionError("Trying to start a test that doesn't exist!");
403         }
404         FragmentManager fm = getFragmentManager();
405         FragmentTransaction transaction = fm.beginTransaction();
406         transaction.replace(R.id.contentFragment, fragment);
407         transaction.commit();
408     }
409 
410     /**
411      * Loads start instruction fragment for a particular test.
412      *
413      * @param phase test to show instruction screen for.
414      */
switchToStartFragment(CTSTest phase)415     public void switchToStartFragment(CTSTest phase) {
416         Log.e(TAG, "switchToStartFragment");
417         mUiListener = null;
418         mAccuracyListener = null;
419         mRobustnessListener = null;
420         mComplexMovementListener = null;
421 
422         mCurrentTest = phase;
423         mDataFragment.startTest(mCurrentTest);
424         Fragment fragment = PhaseStartFragment.newInstance(phase);
425         FragmentManager fm = getFragmentManager();
426         FragmentTransaction transaction = fm.beginTransaction();
427         transaction.replace(R.id.contentFragment, fragment);
428         transaction.commit();
429     }
430 
431     @Override
onSaveInstanceState(Bundle outState)432     protected void onSaveInstanceState(Bundle outState) {
433         // We are always going to be restarting if this is called.
434         outState.putBoolean(EXTRA_ON_RESTART, true);
435         super.onSaveInstanceState(outState);
436     }
437 
438     @Override
onDestroy()439     protected void onDestroy() {
440         super.onDestroy();
441         onDestroyUi();
442         mUiListener = null;
443         mAccuracyListener = null;
444         mRobustnessListener = null;
445         mComplexMovementListener = null;
446         mDataFragment = null;
447     }
448 
449     @Override
onDestroyUi()450     public void onDestroyUi() {
451         if (mUiListener != null) {
452             mUiListener.onDestroyUi();
453         }
454     }
455 }
456