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