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.camera.its; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.res.Configuration; 24 import android.hardware.camera2.CameraAccessException; 25 import android.hardware.camera2.CameraCharacteristics; 26 import android.hardware.camera2.CameraManager; 27 import android.os.Bundle; 28 import android.text.method.ScrollingMovementMethod; 29 import android.util.Log; 30 import android.view.WindowManager; 31 import android.widget.TextView; 32 import android.widget.Toast; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Comparator; 37 import java.util.HashSet; 38 import java.util.HashMap; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Set; 42 import java.util.TreeSet; 43 import java.io.BufferedReader; 44 import java.io.FileReader; 45 import java.io.FileNotFoundException; 46 import java.io.IOException; 47 48 import com.android.compatibility.common.util.ResultType; 49 import com.android.compatibility.common.util.ResultUnit; 50 import com.android.cts.verifier.ArrayTestListAdapter; 51 import com.android.cts.verifier.DialogTestListActivity; 52 import com.android.cts.verifier.R; 53 import com.android.cts.verifier.TestResult; 54 55 import org.json.JSONArray; 56 import org.json.JSONObject; 57 58 /** 59 * Test for Camera features that require that the camera be aimed at a specific test scene. 60 * This test activity requires a USB connection to a computer, and a corresponding host-side run of 61 * the python scripts found in the CameraITS directory. 62 */ 63 public class ItsTestActivity extends DialogTestListActivity { 64 private static final String TAG = "ItsTestActivity"; 65 private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID"; 66 private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS"; 67 private static final String EXTRA_VERSION = "camera.its.extra.VERSION"; 68 private static final String CURRENT_VERSION = "1.0"; 69 private static final String ACTION_ITS_RESULT = 70 "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT"; 71 72 private static final String RESULT_PASS = "PASS"; 73 private static final String RESULT_FAIL = "FAIL"; 74 private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED"; 75 private static final Set<String> RESULT_VALUES = new HashSet<String>( 76 Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED})); 77 private static final int MAX_SUMMARY_LEN = 200; 78 79 private final ResultReceiver mResultsReceiver = new ResultReceiver(); 80 81 // Initialized in onCreate 82 ArrayList<String> mNonLegacyCameraIds = null; 83 84 // Scenes 85 private static final ArrayList<String> mSceneIds = new ArrayList<String> () { { 86 add("scene0"); 87 add("scene1"); 88 add("scene2"); 89 add("scene3"); 90 add("scene4"); 91 add("scene5"); 92 } }; 93 94 // TODO: cache the following in saved bundle 95 private Set<ResultKey> mAllScenes = null; 96 // (camera, scene) -> (pass, fail) 97 private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>(); 98 // map camera id to ITS summary report path 99 private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>(); 100 101 final class ResultKey { 102 public final String cameraId; 103 public final String sceneId; 104 ResultKey(String cameraId, String sceneId)105 public ResultKey(String cameraId, String sceneId) { 106 this.cameraId = cameraId; 107 this.sceneId = sceneId; 108 } 109 110 @Override equals(final Object o)111 public boolean equals(final Object o) { 112 if (o == null) return false; 113 if (this == o) return true; 114 if (o instanceof ResultKey) { 115 final ResultKey other = (ResultKey) o; 116 return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId); 117 } 118 return false; 119 } 120 121 @Override hashCode()122 public int hashCode() { 123 int h = cameraId.hashCode(); 124 h = ((h << 5) - h) ^ sceneId.hashCode(); 125 return h; 126 } 127 } 128 ItsTestActivity()129 public ItsTestActivity() { 130 super(R.layout.its_main, 131 R.string.camera_its_test, 132 R.string.camera_its_test_info, 133 R.string.camera_its_test); 134 } 135 136 private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() { 137 @Override 138 public int compare(ResultKey k1, ResultKey k2) { 139 if (k1.cameraId.equals(k2.cameraId)) 140 return k1.sceneId.compareTo(k2.sceneId); 141 return k1.cameraId.compareTo(k2.cameraId); 142 } 143 }; 144 145 class ResultReceiver extends BroadcastReceiver { 146 @Override onReceive(Context context, Intent intent)147 public void onReceive(Context context, Intent intent) { 148 Log.i(TAG, "Received result for Camera ITS tests"); 149 if (ACTION_ITS_RESULT.equals(intent.getAction())) { 150 String version = intent.getStringExtra(EXTRA_VERSION); 151 if (version == null || !version.equals(CURRENT_VERSION)) { 152 Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION + 153 ", got " + ((version == null) ? "null" : version)); 154 ItsTestActivity.this.showToast(R.string.its_version_mismatch); 155 return; 156 } 157 158 String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID); 159 String results = intent.getStringExtra(EXTRA_RESULTS); 160 if (cameraId == null || results == null) { 161 Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) + 162 ", results = " + ((results == null) ? "null" : results)); 163 return; 164 } 165 166 if (!mNonLegacyCameraIds.contains(cameraId)) { 167 Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS"); 168 return; 169 } 170 171 try { 172 /* Sample JSON results string 173 { 174 "scene0":{ 175 "result":"PASS", 176 "summary":"/sdcard/cam0_scene0.txt" 177 }, 178 "scene1":{ 179 "result":"NOT_EXECUTED" 180 }, 181 "scene2":{ 182 "result":"FAIL", 183 "summary":"/sdcard/cam0_scene2.txt" 184 } 185 } 186 */ 187 JSONObject jsonResults = new JSONObject(results); 188 Set<String> scenes = new HashSet<>(); 189 Iterator<String> keys = jsonResults.keys(); 190 while (keys.hasNext()) { 191 scenes.add(keys.next()); 192 } 193 boolean newScenes = false; 194 if (mAllScenes == null) { 195 mAllScenes = new TreeSet<>(mComparator); 196 newScenes = true; 197 } else { // See if scene lists changed 198 for (String scene : scenes) { 199 if (!mAllScenes.contains(new ResultKey(cameraId, scene))) { 200 // Scene list changed. Cleanup previous test results 201 newScenes = true; 202 break; 203 } 204 } 205 for (ResultKey k : mAllScenes) { 206 if (!scenes.contains(k.sceneId)) { 207 newScenes = true; 208 break; 209 } 210 } 211 } 212 if (newScenes) { 213 mExecutedScenes.clear(); 214 mAllScenes.clear(); 215 for (String scene : scenes) { 216 for (String c : mNonLegacyCameraIds) { 217 mAllScenes.add(new ResultKey(c, scene)); 218 } 219 } 220 } 221 222 // Update test execution results 223 for (String scene : scenes) { 224 JSONObject sceneResult = jsonResults.getJSONObject(scene); 225 String result = sceneResult.getString("result"); 226 if (result == null) { 227 Log.e(TAG, "Result for " + scene + " is null"); 228 return; 229 } 230 Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result); 231 if (!RESULT_VALUES.contains(result)) { 232 Log.e(TAG, "Unknown result for " + scene + ": " + result); 233 return; 234 } 235 ResultKey key = new ResultKey(cameraId, scene); 236 if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) { 237 boolean pass = result.equals(RESULT_PASS); 238 mExecutedScenes.put(key, pass); 239 setTestResult(testId(cameraId, scene), pass ? 240 TestResult.TEST_RESULT_PASSED : TestResult.TEST_RESULT_FAILED); 241 Log.e(TAG, "setTestResult for " + testId(cameraId, scene) + ": " + result); 242 String summary = sceneResult.optString("summary"); 243 if (!summary.equals("")) { 244 mSummaryMap.put(key, summary); 245 } 246 } // do nothing for NOT_EXECUTED scenes 247 } 248 } catch (org.json.JSONException e) { 249 Log.e(TAG, "Error reading json result string:" + results , e); 250 return; 251 } 252 253 // Set summary if all scenes reported 254 if (mSummaryMap.keySet().containsAll(mAllScenes)) { 255 StringBuilder summary = new StringBuilder(); 256 for (String path : mSummaryMap.values()) { 257 appendFileContentToSummary(summary, path); 258 } 259 if (summary.length() > MAX_SUMMARY_LEN) { 260 Log.w(TAG, "ITS summary report too long: len: " + summary.length()); 261 } 262 ItsTestActivity.this.getReportLog().setSummary( 263 summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE); 264 } 265 266 // Display current progress 267 StringBuilder progress = new StringBuilder(); 268 for (ResultKey k : mAllScenes) { 269 String status = RESULT_NOT_EXECUTED; 270 if (mExecutedScenes.containsKey(k)) { 271 status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL; 272 } 273 progress.append(String.format("Cam %s, %s: %s\n", 274 k.cameraId, k.sceneId, status)); 275 } 276 TextView progressView = (TextView) findViewById(R.id.its_progress); 277 progressView.setMovementMethod(new ScrollingMovementMethod()); 278 progressView.setText(progress.toString()); 279 280 281 // Enable pass button if all scenes pass 282 boolean allScenesPassed = true; 283 for (ResultKey k : mAllScenes) { 284 Boolean pass = mExecutedScenes.get(k); 285 if (pass == null || pass == false) { 286 allScenesPassed = false; 287 break; 288 } 289 } 290 if (allScenesPassed) { 291 // Enable pass button 292 ItsTestActivity.this.showToast(R.string.its_test_passed); 293 ItsTestActivity.this.getPassButton().setEnabled(true); 294 ItsTestActivity.this.setTestResultAndFinish(true); 295 } else { 296 ItsTestActivity.this.getPassButton().setEnabled(false); 297 } 298 } 299 } 300 appendFileContentToSummary(StringBuilder summary, String path)301 private void appendFileContentToSummary(StringBuilder summary, String path) { 302 BufferedReader reader = null; 303 try { 304 reader = new BufferedReader(new FileReader(path)); 305 String line = null; 306 do { 307 line = reader.readLine(); 308 if (line != null) { 309 summary.append(line); 310 } 311 } while (line != null); 312 } catch (FileNotFoundException e) { 313 Log.e(TAG, "Cannot find ITS summary file at " + path); 314 summary.append("Cannot find ITS summary file at " + path); 315 } catch (IOException e) { 316 Log.e(TAG, "IO exception when trying to read " + path); 317 summary.append("IO exception when trying to read " + path); 318 } finally { 319 if (reader != null) { 320 try { 321 reader.close(); 322 } catch (IOException e) { 323 } 324 } 325 } 326 } 327 } 328 329 @Override onCreate(Bundle savedInstanceState)330 protected void onCreate(Bundle savedInstanceState) { 331 // Hide the test if all camera devices are legacy 332 CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE); 333 try { 334 String[] cameraIds = manager.getCameraIdList(); 335 mNonLegacyCameraIds = new ArrayList<String>(); 336 boolean allCamerasAreLegacy = true; 337 for (String id : cameraIds) { 338 CameraCharacteristics characteristics = manager.getCameraCharacteristics(id); 339 if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) 340 != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { 341 mNonLegacyCameraIds.add(id); 342 allCamerasAreLegacy = false; 343 } 344 } 345 if (allCamerasAreLegacy) { 346 showToast(R.string.all_legacy_devices); 347 ItsTestActivity.this.getReportLog().setSummary( 348 "PASS: all cameras on this device are LEGACY" 349 , 1.0, ResultType.NEUTRAL, ResultUnit.NONE); 350 setTestResultAndFinish(true); 351 } 352 } catch (CameraAccessException e) { 353 Toast.makeText(ItsTestActivity.this, 354 "Received error from camera service while checking device capabilities: " 355 + e, Toast.LENGTH_SHORT).show(); 356 } 357 358 super.onCreate(savedInstanceState); 359 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 360 } 361 362 @Override showManualTestDialog(final DialogTestListItem test, final DialogTestListItem.TestCallback callback)363 public void showManualTestDialog(final DialogTestListItem test, 364 final DialogTestListItem.TestCallback callback) { 365 //Nothing todo for ITS 366 } 367 testTitle(String cam, String scene)368 protected String testTitle(String cam, String scene) { 369 return "Camera: " + cam + ", " + scene; 370 } 371 testId(String cam, String scene)372 protected String testId(String cam, String scene) { 373 return "Camera_ITS_" + cam + "_" + scene; 374 } 375 setupItsTests(ArrayTestListAdapter adapter)376 protected void setupItsTests(ArrayTestListAdapter adapter) { 377 for (String cam : mNonLegacyCameraIds) { 378 for (String scene : mSceneIds) { 379 adapter.add(new DialogTestListItem(this, 380 testTitle(cam, scene), 381 testId(cam, scene))); 382 } 383 } 384 } 385 386 @Override setupTests(ArrayTestListAdapter adapter)387 protected void setupTests(ArrayTestListAdapter adapter) { 388 setupItsTests(adapter); 389 } 390 391 @Override onResume()392 protected void onResume() { 393 super.onResume(); 394 CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE); 395 if (manager == null) { 396 showToast(R.string.no_camera_manager); 397 } else { 398 Log.d(TAG, "register ITS result receiver"); 399 IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT); 400 registerReceiver(mResultsReceiver, filter); 401 } 402 } 403 404 @Override onPause()405 protected void onPause() { 406 super.onPause(); 407 Log.d(TAG, "unregister ITS result receiver"); 408 unregisterReceiver(mResultsReceiver); 409 } 410 411 @Override onConfigurationChanged(Configuration newConfig)412 public void onConfigurationChanged(Configuration newConfig) { 413 super.onConfigurationChanged(newConfig); 414 setContentView(R.layout.its_main); 415 setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1); 416 setPassFailButtonClickListeners(); 417 } 418 } 419