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