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