blob: 33f8f2c892d74309afb996fcdc111254c56f8e33 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.verifier.camera.its;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.camera2.CameraManager;
import android.mediapc.cts.common.PerformanceClassEvaluator;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.cts.verifier.ArrayTestListAdapter;
import com.android.cts.verifier.DialogTestListActivity;
import com.android.cts.verifier.R;
import com.android.cts.verifier.TestResult;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.rules.TestName;
/**
* Test for Camera features that require that the camera be aimed at a specific test scene.
* This test activity requires a USB connection to a computer, and a corresponding host-side run of
* the python scripts found in the CameraITS directory.
*/
public class ItsTestActivity extends DialogTestListActivity {
private static final String TAG = "ItsTestActivity";
private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
private static final String EXTRA_VERSION = "camera.its.extra.VERSION";
private static final String CURRENT_VERSION = "1.0";
private static final String ACTION_ITS_RESULT =
"com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
private static final String RESULT_PASS = "PASS";
private static final String RESULT_FAIL = "FAIL";
private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED";
private static final Set<String> RESULT_VALUES = new HashSet<String>(
Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
private static final int MAX_SUMMARY_LEN = 200;
private static final Pattern MPC12_CAMERA_LAUNCH_PATTERN =
Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
private final ResultReceiver mResultsReceiver = new ResultReceiver();
private boolean mReceiverRegistered = false;
public final TestName mTestName = new TestName();
// Initialized in onCreate
List<String> mToBeTestedCameraIds = null;
String mPrimaryRearCameraId = null;
String mPrimaryFrontCameraId = null;
// Scenes
private static final List<String> mSceneIds = List.of(
"scene0",
"scene1_1",
"scene1_2",
"scene2_a",
"scene2_b",
"scene2_c",
"scene2_d",
"scene2_e",
"scene3",
"scene4",
"scene5",
"scene6",
"sensor_fusion");
// This must match scenes of SUB_CAMERA_TESTS in tools/run_all_tests.py
private static final List<String> mHiddenPhysicalCameraSceneIds = List.of(
"scene0",
"scene1_1",
"scene1_2",
"scene2_a",
"scene4",
"sensor_fusion");
// TODO: cache the following in saved bundle
private Set<ResultKey> mAllScenes = null;
// (camera, scene) -> (pass, fail)
private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
// map camera id to ITS summary report path
private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
// All primary cameras for which MPC level test has run
private Set<ResultKey> mExecutedMpcTests = null;
private static final String MPC_LAUNCH_REQ_NUM = "2.2.7.2/7.5/H-1-6";
private static final String MPC_JPEG_CAPTURE_REQ_NUM = "2.2.7.2/7.5/H-1-5";
// Performance class evaluator used for writing test result
PerformanceClassEvaluator mPce = new PerformanceClassEvaluator(mTestName);
PerformanceClassEvaluator.CameraLatencyRequirement mJpegLatencyReq =
mPce.addR7_5__H_1_5();
PerformanceClassEvaluator.CameraLatencyRequirement mLaunchLatencyReq =
mPce.addR7_5__H_1_6();
final class ResultKey {
public final String cameraId;
public final String sceneId;
public ResultKey(String cameraId, String sceneId) {
this.cameraId = cameraId;
this.sceneId = sceneId;
}
@Override
public boolean equals(final Object o) {
if (o == null) return false;
if (this == o) return true;
if (o instanceof ResultKey) {
final ResultKey other = (ResultKey) o;
return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId);
}
return false;
}
@Override
public int hashCode() {
int h = cameraId.hashCode();
h = ((h << 5) - h) ^ sceneId.hashCode();
return h;
}
}
public ItsTestActivity() {
super(R.layout.its_main,
R.string.camera_its_test,
R.string.camera_its_test_info,
R.string.camera_its_test);
}
private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
@Override
public int compare(ResultKey k1, ResultKey k2) {
if (k1.cameraId.equals(k2.cameraId))
return k1.sceneId.compareTo(k2.sceneId);
return k1.cameraId.compareTo(k2.cameraId);
}
};
class ResultReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received result for Camera ITS tests");
if (ACTION_ITS_RESULT.equals(intent.getAction())) {
String version = intent.getStringExtra(EXTRA_VERSION);
if (version == null || !version.equals(CURRENT_VERSION)) {
Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION +
", got " + ((version == null) ? "null" : version));
ItsTestActivity.this.showToast(R.string.its_version_mismatch);
return;
}
String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
String results = intent.getStringExtra(EXTRA_RESULTS);
if (cameraId == null || results == null) {
Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) +
", results = " + ((results == null) ? "null" : results));
return;
}
if (!mToBeTestedCameraIds.contains(cameraId)) {
Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
return;
}
try {
/* Sample JSON results string
{
"scene0":{
"result":"PASS",
"summary":"/sdcard/cam0_scene0.txt"
},
"scene1":{
"result":"NOT_EXECUTED"
},
"scene2":{
"result":"FAIL",
"summary":"/sdcard/cam0_scene2.txt"
}
}
*/
JSONObject jsonResults = new JSONObject(results);
Log.d(TAG,"Results received:" + jsonResults.toString());
Set<String> scenes = new HashSet<>();
Iterator<String> keys = jsonResults.keys();
while (keys.hasNext()) {
scenes.add(keys.next());
}
// Update test execution results
for (String scene : scenes) {
JSONObject sceneResult = jsonResults.getJSONObject(scene);
Log.v(TAG, sceneResult.toString());
String result = sceneResult.getString("result");
if (result == null) {
Log.e(TAG, "Result for " + scene + " is null");
return;
}
Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result);
if (!RESULT_VALUES.contains(result)) {
Log.e(TAG, "Unknown result for " + scene + ": " + result);
return;
}
ResultKey key = new ResultKey(cameraId, scene);
if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
boolean pass = result.equals(RESULT_PASS);
mExecutedScenes.put(key, pass);
// Get start/end time per camera/scene for result history collection.
mStartTime = sceneResult.getLong("start");
mEndTime = sceneResult.getLong("end");
setTestResult(testId(cameraId, scene), pass ?
TestResult.TEST_RESULT_PASSED : TestResult.TEST_RESULT_FAILED);
Log.e(TAG, "setTestResult for " + testId(cameraId, scene) + ": " + result);
String summary = sceneResult.optString("summary");
if (!summary.equals("")) {
mSummaryMap.put(key, summary);
}
} // do nothing for NOT_EXECUTED scenes
if (sceneResult.isNull("mpc_metrics")) {
continue;
}
// Update MPC level
JSONArray metrics = sceneResult.getJSONArray("mpc_metrics");
for (int i = 0; i < metrics.length(); i++) {
String mpcResult = metrics.getString(i);
if (!matchMpcResult(cameraId, mpcResult)) {
Log.e(TAG, "Error parsing MPC result string:" + mpcResult);
return;
}
}
}
} catch (org.json.JSONException e) {
Log.e(TAG, "Error reading json result string:" + results , e);
return;
}
// Set summary if all scenes reported
if (mSummaryMap.keySet().containsAll(mAllScenes)) {
// Save test summary
StringBuilder summary = new StringBuilder();
for (String path : mSummaryMap.values()) {
appendFileContentToSummary(summary, path);
}
if (summary.length() > MAX_SUMMARY_LEN) {
Log.w(TAG, "ITS summary report too long: len: " + summary.length());
}
ItsTestActivity.this.getReportLog().setSummary(
summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
}
// Display current progress
StringBuilder progress = new StringBuilder();
for (ResultKey k : mAllScenes) {
String status = RESULT_NOT_EXECUTED;
if (mExecutedScenes.containsKey(k)) {
status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL;
}
progress.append(String.format("Cam %s, %s: %s\n",
k.cameraId, k.sceneId, status));
}
TextView progressView = (TextView) findViewById(R.id.its_progress);
progressView.setMovementMethod(new ScrollingMovementMethod());
progressView.setText(progress.toString());
// Enable pass button if all scenes pass
boolean allScenesPassed = true;
for (ResultKey k : mAllScenes) {
Boolean pass = mExecutedScenes.get(k);
if (pass == null || pass == false) {
allScenesPassed = false;
break;
}
}
if (allScenesPassed) {
Log.i(TAG, "All scenes passed.");
// Enable pass button
ItsTestActivity.this.getPassButton().setEnabled(true);
ItsTestActivity.this.setTestResultAndFinish(true);
} else {
ItsTestActivity.this.getPassButton().setEnabled(false);
}
}
}
private void appendFileContentToSummary(StringBuilder summary, String path) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(path));
String line = null;
do {
line = reader.readLine();
if (line != null) {
summary.append(line);
}
} while (line != null);
} catch (FileNotFoundException e) {
Log.e(TAG, "Cannot find ITS summary file at " + path);
summary.append("Cannot find ITS summary file at " + path);
} catch (IOException e) {
Log.e(TAG, "IO exception when trying to read " + path);
summary.append("IO exception when trying to read " + path);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
}
private boolean matchMpcResult(String cameraId, String mpcResult) {
Matcher launchMatcher = MPC12_CAMERA_LAUNCH_PATTERN.matcher(mpcResult);
boolean launchMatches = launchMatcher.matches();
Matcher jpegMatcher = MPC12_JPEG_CAPTURE_PATTERN.matcher(mpcResult);
boolean jpegMatches = jpegMatcher.matches();
if (!launchMatches && !jpegMatches) {
return false;
}
if (!cameraId.equals(mPrimaryRearCameraId) &&
!cameraId.equals(mPrimaryFrontCameraId)) {
return false;
}
if (launchMatches) {
float latency = Float.parseFloat(launchMatcher.group(1));
if (cameraId.equals(mPrimaryRearCameraId)) {
mLaunchLatencyReq.setRearCameraLatency(latency);
} else {
mLaunchLatencyReq.setFrontCameraLatency(latency);
}
mExecutedMpcTests.add(new ResultKey(cameraId, MPC_LAUNCH_REQ_NUM));
} else {
float latency = Float.parseFloat(jpegMatcher.group(1));
if (cameraId.equals(mPrimaryRearCameraId)) {
mJpegLatencyReq.setRearCameraLatency(latency);
} else {
mJpegLatencyReq.setFrontCameraLatency(latency);
}
mExecutedMpcTests.add(new ResultKey(cameraId, MPC_JPEG_CAPTURE_REQ_NUM));
}
// Save MPC info once both front primary and rear primary data are collected.
if (mExecutedMpcTests.size() == 4) {
mPce.submit();
}
return true;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// Hide the test if all camera devices are legacy
CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
try {
ItsUtils.ItsCameraIdList cameraIdList =
ItsUtils.getItsCompatibleCameraIds(manager);
mToBeTestedCameraIds = cameraIdList.mCameraIdCombos;
mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
} catch (ItsException e) {
Toast.makeText(ItsTestActivity.this,
"Received error from camera service while checking device capabilities: "
+ e, Toast.LENGTH_SHORT).show();
}
super.onCreate(savedInstanceState);
if (mToBeTestedCameraIds.size() == 0) {
showToast(R.string.all_exempted_devices);
ItsTestActivity.this.getReportLog().setSummary(
"PASS: all cameras on this device are exempted from ITS"
, 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
setTestResultAndFinish(true);
}
// Default locale must be set to "en-us"
Locale locale = Locale.getDefault();
if (!Locale.US.equals(locale)) {
String toastMessage = "Unsupported default language " + locale + "! " +
"Please switch the default language to English (United States) in " +
"Settings > Language & input > Languages";
Toast.makeText(ItsTestActivity.this, toastMessage, Toast.LENGTH_LONG).show();
ItsTestActivity.this.getReportLog().setSummary(
"FAIL: Default language is not set to " + Locale.US,
1.0, ResultType.NEUTRAL, ResultUnit.NONE);
setTestResultAndFinish(false);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public void showManualTestDialog(final DialogTestListItem test,
final DialogTestListItem.TestCallback callback) {
//Nothing todo for ITS
}
protected String testTitle(String cam, String scene) {
return "Camera: " + cam + ", " + scene;
}
protected String testId(String cam, String scene) {
return "Camera_ITS_" + cam + "_" + scene;
}
protected void setupItsTests(ArrayTestListAdapter adapter) {
for (String cam : mToBeTestedCameraIds) {
List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER) ?
mHiddenPhysicalCameraSceneIds : mSceneIds;
for (String scene : scenes) {
// Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
// devices with sub-cameras.
if(mAllScenes == null){
mAllScenes = new TreeSet<>(mComparator);
}
mAllScenes.add(new ResultKey(cam, scene));
adapter.add(new DialogTestListItem(this,
testTitle(cam, scene),
testId(cam, scene)));
}
if (mExecutedMpcTests == null) {
mExecutedMpcTests = new TreeSet<>(mComparator);
}
Log.d(TAG,"Total combinations to test on this device:" + mAllScenes.size());
}
}
@Override
protected void setupTests(ArrayTestListAdapter adapter) {
setupItsTests(adapter);
}
@Override
protected void onResume() {
super.onResume();
CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
if (manager == null) {
showToast(R.string.no_camera_manager);
} else {
Log.d(TAG, "register ITS result receiver");
IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
registerReceiver(mResultsReceiver, filter);
mReceiverRegistered = true;
}
}
@Override
public void onDestroy() {
Log.d(TAG, "unregister ITS result receiver");
if (mReceiverRegistered) {
unregisterReceiver(mResultsReceiver);
}
super.onDestroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.its_main);
setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
setPassFailButtonClickListeners();
}
}