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.ActivityNotFoundException;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.hardware.camera2.CameraManager;
28 import android.hardware.cts.helpers.CameraUtils;
29 import android.hardware.devicestate.DeviceState;
30 import android.hardware.devicestate.DeviceStateManager;
31 import android.mediapc.cts.common.CameraRequirement;
32 import android.mediapc.cts.common.PerformanceClassEvaluator;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.provider.MediaStore;
38 import android.text.method.ScrollingMovementMethod;
39 import android.util.Log;
40 import android.util.Pair;
41 import android.view.WindowManager;
42 import android.widget.TextView;
43 import android.widget.Toast;
44 
45 import androidx.core.content.FileProvider;
46 
47 import com.android.compatibility.common.util.ResultType;
48 import com.android.compatibility.common.util.ResultUnit;
49 import com.android.cts.verifier.ArrayTestListAdapter;
50 import com.android.cts.verifier.CtsVerifierReportLog;
51 import com.android.cts.verifier.DialogTestListActivity;
52 import com.android.cts.verifier.R;
53 import com.android.cts.verifier.TestResult;
54 import com.android.internal.util.ArrayUtils;
55 
56 import org.json.JSONArray;
57 import org.json.JSONObject;
58 import org.junit.rules.TestName;
59 
60 import java.io.BufferedReader;
61 import java.io.File;
62 import java.io.FileNotFoundException;
63 import java.io.FileReader;
64 import java.io.IOException;
65 import java.math.BigDecimal;
66 import java.time.Instant;
67 import java.time.ZoneId;
68 import java.time.format.DateTimeFormatter;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.Collections;
72 import java.util.Comparator;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Locale;
78 import java.util.Set;
79 import java.util.TreeSet;
80 import java.util.concurrent.Executor;
81 import java.util.concurrent.LinkedBlockingQueue;
82 import java.util.regex.Matcher;
83 import java.util.regex.Pattern;
84 
85 /**
86  * Test for Camera features that require that the camera be aimed at a specific test scene.
87  * This test activity requires a USB connection to a computer, and a corresponding host-side run of
88  * the python scripts found in the CameraITS directory.
89  */
90 public class ItsTestActivity extends DialogTestListActivity {
91     private static final String TAG = "ItsTestActivity";
92     private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
93     private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
94     private static final String EXTRA_VERSION = "camera.its.extra.VERSION";
95     private static final String CURRENT_VERSION = "1.0";
96     private static final String ACTION_ITS_RESULT =
97             "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
98     private static final String ACTION_ITS_DO_JCA_CAPTURE =
99             "com.android.cts.verifier.camera.its.ACTION_ITS_DO_JCA_CAPTURE";
100     private static final int REQUEST_IMAGE_CAPTURE = 1;
101     private static final String JCA_PACKAGE_NAME = "com.google.jetpackcamera";
102     private static final String JCA_ACTIVITY_NAME = "MainActivity";
103     private static final String JCA_FILES_CHILD_PATHNAME = "Images/JCATestCaptures";
104     public static final String JCA_CAPTURE_PATH_TAG = "JCA_CAPTURE_PATH";
105     public static final String JCA_CAPTURE_STATUS_TAG = "JCA_CAPTURE_STATUS";
106 
107     private static final String RESULT_PASS = "PASS";
108     private static final String RESULT_FAIL = "FAIL";
109     private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED";
110     private static final Set<String> RESULT_VALUES = new HashSet<String>(
111             Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
112     private static final int MAX_SUMMARY_LEN = 200;
113 
114     private static final Pattern MPC12_CAMERA_LAUNCH_PATTERN =
115             Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
116     private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
117             Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
118     private static final Pattern MPC15_ULTRA_HDR_PATTERN =
119             Pattern.compile("has_gainmap.*");
120     private static final int AVAILABILITY_TIMEOUT_MS = 10;
121 
122     private static final Pattern PERF_METRICS_YUV_PLUS_JPEG_PATTERN =
123             Pattern.compile("test_yuv_plus_jpeg_rms_diff:(\\d+(\\.\\d+)?)");
124     /* TODO b/346817862 - More concise regex. */
125     private static final Pattern PERF_METRICS_YUV_PLUS_RAW_PATTERN =
126             Pattern.compile("test_yuv_plus_raw.*");
127 
128     private static final String PERF_METRICS_KEY_PREFIX_YUV_PLUS = "yuv_plus";
129     private static final String PERF_METRICS_KEY_RAW = "raw_";
130     private static final String PERF_METRICS_KEY_RAW10 = "raw10";
131     private static final String PERF_METRICS_KEY_RAW12 = "raw12";
132     private static final String PERF_METRICS_KEY_RMS_DIFF = "rms_diff";
133 
134     private static final Pattern PERF_METRICS_IMU_DRIFT_PATTERN =
135             Pattern.compile("test_imu_drift_.*");
136     private static final Pattern PERF_METRICS_SENSOR_FUSION_PATTERN =
137             Pattern.compile("test_sensor_fusion_.*");
138 
139     private static final String PERF_METRICS_KEY_PREFIX_SENSOR_FUSION = "sensor_fusion";
140     private static final String PERF_METRICS_KEY_CORR_DIST = "corr_dist";
141     private static final String PERF_METRICS_KEY_OFFSET_MS = "offset_ms";
142 
143     private static final Pattern PERF_METRICS_BURST_CAPTURE_PATTERN =
144             Pattern.compile("test_burst_capture_.*");
145 
146     private static final String PERF_METRICS_KEY_PREFIX_BURST_CAPTURE = "burst_capture";
147     private static final String PERF_METRICS_KEY_FRAMEDURATION =
148             "max_frame_time_minus_frameduration_ns";
149 
150     private static final Pattern PERF_METRICS_LOW_LIGHT_BOOST_PATTERN =
151             Pattern.compile("test_low_light_boost_.*");
152     private static final Pattern PERF_METRICS_EXTENSION_NIGHT_MODE_PATTERN =
153             Pattern.compile("test_night_extension_.*");
154 
155     private static final String PERF_METRICS_KEY_CHART_LUMA = "chart_luma";
156     private static final String PERF_METRICS_KEY_AVG_LUMA = "avg_luma";
157     private static final String PERF_METRICS_KEY_DELTA_AVG_LUMA = "delta_avg_luma";
158     private static final String PERF_METRICS_KEY_PREFIX_NIGHT = "night_extension";
159     private static final String PERF_METRICS_KEY_PREFIX_LOW_LIGHT = "low_light_boost";
160 
161     private static final Pattern PERF_METRICS_DISTORTION_PATTERN =
162             Pattern.compile("test_preview_distortion_.*");
163 
164     private static final Pattern PERF_METRICS_INTRINSIC_PATTERN =
165             Pattern.compile("test_lens_intrinsic_calibration_.*");
166 
167     private static final Pattern PERF_METRICS_AEAWB_PATTERN =
168             Pattern.compile("test_ae_awb_regions_.*");
169     private static final String REPORT_LOG_NAME = "CtsCameraItsTestCases";
170 
171     private static final String ZOOM = "zoom";
172     private static final String TEST_PATTERN = "^test_";
173     private static final Pattern PERF_METRICS_MULTICAM_PATTERN =
174             Pattern.compile("test_multi_camera_switch_.*");
175 
176     private static final Pattern PERF_METRICS_PREVIEW_FRAME_DROP_PATTERN =
177             Pattern.compile("test_preview_frame_drop_.*");
178 
179     private static final String PERF_METRICS_KEY_MAX_DELTA = "max_delta";
180     private static final String PERF_METRICS_KEY_PREFIX_PREVIEW_FRAME_DROP =
181             "preview_frame_drop";
182 
183     private final ResultReceiver mResultsReceiver = new ResultReceiver();
184     private final BroadcastReceiver mCommandReceiver = new BroadcastReceiver() {
185         @Override
186         public void onReceive(Context context, Intent intent) {
187             Logt.i(TAG, "Received ITS test command");
188             if (ACTION_ITS_DO_JCA_CAPTURE.equals(intent.getAction())) {
189                 Logt.i(TAG, "Doing JCA intent capture");
190                 doJcaCapture();
191             } else {
192                 Logt.e(TAG, "Unknown intent action " + intent.getAction());
193             }
194         }
195     };
196     private String mJcaCapturePath = "";
197     private boolean mReceiverRegistered = false;
198 
199     public final TestName mTestName = new TestName();
200     private  boolean mIsFoldableDevice = false;
201     private  boolean mIsDeviceFolded = false;
202     private  boolean mFoldedTestSetupDone = false;
203     private  boolean mUnfoldedTestSetupDone = false;
204     private  Set<Pair<String, String>> mUnavailablePhysicalCameras =
205             new HashSet<Pair<String, String>>();
206     private CameraManager mCameraManager = null;
207     private DeviceStateManager mDeviceStateManager = null;
208     private HandlerThread mCameraThread = null;
209     private Handler mCameraHandler = null;
210 
211     // Initialized in onCreate
212     List<String> mToBeTestedCameraIds = null;
213     private  String mPrimaryRearCameraId = null;
214     private  String mPrimaryFrontCameraId = null;
215     private  List<String> mToBeTestedCameraIdsUnfolded = null;
216     private  List<String> mToBeTestedCameraIdsFolded = null;
217     private  String mPrimaryRearCameraIdUnfolded = null;
218     private  String mPrimaryFrontCameraIdUnfolded = null;
219     private ArrayTestListAdapter mAdapter;
220 
221     // Scenes
222     private static final List<String> mSceneIds = List.of(
223             "scene0",
224             "scene1_1",
225             "scene1_2",
226             "scene2_a",
227             "scene2_b",
228             "scene2_c",
229             "scene2_d",
230             "scene2_e",
231             "scene2_f",
232             "scene3",
233             "scene4",
234             "scene5",
235             "scene6",
236             "scene7",
237             "scene8",
238             "scene9",
239             "scene_extensions/scene_hdr",
240             "scene_extensions/scene_low_light",
241             "scene_video",
242             "sensor_fusion",
243             "feature_combination",
244             "scene_flash");
245 
246     // This must match scenes of SUB_CAMERA_TESTS in tools/run_all_tests.py
247     private static final List<String> mHiddenPhysicalCameraSceneIds = List.of(
248             "scene0",
249             "scene1_1",
250             "scene1_2",
251             "scene2_a",
252             "scene4",
253             "scene_video",
254             "sensor_fusion");
255 
256     // TODO: cache the following in saved bundle
257     private Set<ResultKey> mAllScenes = null;
258     // (camera, scene) -> (pass, fail)
259     private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
260     // map camera id to ITS summary report path
261     private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
262     // All primary cameras for which MPC level test has run
263     private Set<ResultKey> mExecutedMpcTests = null;
264     private static final String MPC_LAUNCH_REQ_NUM = "2.2.7.2/7.5/H-1-6";
265     private static final String MPC_JPEG_CAPTURE_REQ_NUM = "2.2.7.2/7.5/H-1-5";
266     private static final String MPC_ULTRA_HDR_REQ_NUM = "2.2.7.2/7.5/H-1-20";
267     // Performance class evaluator used for writing test result
268     PerformanceClassEvaluator mPce = new PerformanceClassEvaluator(mTestName);
269     CameraRequirement.CameraLatencyRequirement mJpegLatencyReq =
270             mPce.addR7_5__H_1_5();
271     CameraRequirement.CameraLatencyRequirement mLaunchLatencyReq =
272             mPce.addR7_5__H_1_6();
273     CameraRequirement.CameraUltraHdrRequirement mUltraHdrReq =
274             mPce.addR7_5__H_1_20();
275     private CtsVerifierReportLog mReportLog;
276     // Json Array to store all jsob objects with ITS metrics information
277     // stored in the report log
278     private final JSONArray mFinalPerfMetricsArr = new JSONArray();
279 
280     private static class HandlerExecutor implements Executor {
281         private final Handler mHandler;
282 
HandlerExecutor(Handler handler)283         HandlerExecutor(Handler handler) {
284             mHandler = handler;
285         }
286 
287         @Override
execute(Runnable runCmd)288         public void execute(Runnable runCmd) {
289             mHandler.post(runCmd);
290         }
291     }
292 
293     final class ResultKey {
294         public final String cameraId;
295         public final String sceneId;
296 
ResultKey(String cameraId, String sceneId)297         public ResultKey(String cameraId, String sceneId) {
298             this.cameraId = cameraId;
299             this.sceneId = sceneId;
300         }
301 
302         @Override
equals(final Object o)303         public boolean equals(final Object o) {
304             if (o == null) return false;
305             if (this == o) return true;
306             if (o instanceof ResultKey) {
307                 final ResultKey other = (ResultKey) o;
308                 return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId);
309             }
310             return false;
311         }
312 
313         @Override
hashCode()314         public int hashCode() {
315             int h = cameraId.hashCode();
316             h = ((h << 5) - h) ^ sceneId.hashCode();
317             return h;
318         }
319     }
320 
ItsTestActivity()321     public ItsTestActivity() {
322         super(R.layout.its_main,
323                 R.string.camera_its_test,
324                 R.string.camera_its_test_info,
325                 R.string.camera_its_test);
326     }
327 
328     private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
329         @Override
330         public int compare(ResultKey k1, ResultKey k2) {
331             if (k1.cameraId.equals(k2.cameraId))
332                 return k1.sceneId.compareTo(k2.sceneId);
333             return k1.cameraId.compareTo(k2.cameraId);
334         }
335     };
336 
337     class ResultReceiver extends BroadcastReceiver {
338         @Override
onReceive(Context context, Intent intent)339         public void onReceive(Context context, Intent intent) {
340             Log.i(TAG, "Received result for Camera ITS tests");
341             if (ACTION_ITS_RESULT.equals(intent.getAction())) {
342                 String version = intent.getStringExtra(EXTRA_VERSION);
343                 if (version == null || !version.equals(CURRENT_VERSION)) {
344                     Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION +
345                             ", got " + ((version == null) ? "null" : version));
346                     ItsTestActivity.this.showToast(R.string.its_version_mismatch);
347                     return;
348                 }
349 
350                 String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
351                 String results = intent.getStringExtra(EXTRA_RESULTS);
352                 if (cameraId == null || results == null) {
353                     Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) +
354                             ", results = " + ((results == null) ? "null" : results));
355                     return;
356                 }
357 
358                 if (mIsFoldableDevice) {
359                     if (!mIsDeviceFolded) {
360                         if (!mToBeTestedCameraIdsUnfolded.contains(cameraId)) {
361                             Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
362                             return;
363                         }
364                     } else {
365                         if (!mToBeTestedCameraIdsFolded.contains(cameraId)) {
366                             Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
367                             return;
368                         }
369                     }
370                 } else {
371                     if (!mToBeTestedCameraIds.contains(cameraId)) {
372                         Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
373                         return;
374                     }
375                 }
376 
377                 try {
378                     /* Sample JSON results string
379                     {
380                        "scene0":{
381                           "result":"PASS",
382                           "summary":"/sdcard/cam0_scene0.txt"
383                        },
384                        "scene1":{
385                           "result":"NOT_EXECUTED"
386                        },
387                        "scene2":{
388                           "result":"FAIL",
389                           "summary":"/sdcard/cam0_scene2.txt"
390                        }
391                     }
392                     */
393                     JSONObject jsonResults = new JSONObject(results);
394                     Log.d(TAG,"Results received:" + jsonResults.toString());
395                     Set<String> scenes = new HashSet<>();
396                     Iterator<String> keys = jsonResults.keys();
397                     while (keys.hasNext()) {
398                         scenes.add(keys.next());
399                     }
400 
401                     JSONObject camJsonObj = new JSONObject();
402                     camJsonObj.put("camera_id", cameraId);
403                     // Update test execution results
404                     for (String scene : scenes) {
405                         JSONObject sceneResult = jsonResults.getJSONObject(scene);
406                         Log.v(TAG, sceneResult.toString());
407                         String result = sceneResult.getString("result");
408                         if (result == null) {
409                             Log.e(TAG, "Result for " + scene + " is null");
410                             return;
411                         }
412                         Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result);
413                         if (!RESULT_VALUES.contains(result)) {
414                             Log.e(TAG, "Unknown result for " + scene + ": " + result);
415                             return;
416                         }
417                         ResultKey key = new ResultKey(cameraId, scene);
418                         if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
419                             boolean pass = result.equals(RESULT_PASS);
420                             mExecutedScenes.put(key, pass);
421                             // Get start/end time per camera/scene for result history collection.
422                             mStartTime = sceneResult.getLong("start");
423                             mEndTime = sceneResult.getLong("end");
424                             setTestResult(testId(cameraId, scene), pass ?
425                                     TestResult.TEST_RESULT_PASSED : TestResult.TEST_RESULT_FAILED);
426                             Log.e(TAG, "setTestResult for " + testId(cameraId, scene) + ": " + result);
427                             String summary = sceneResult.optString("summary");
428                             if (!summary.equals("")) {
429                                 mSummaryMap.put(key, summary);
430                             }
431                         } // do nothing for NOT_EXECUTED scenes
432 
433                         if (sceneResult.isNull("mpc_metrics")) {
434                             continue;
435                         }
436                         // Update MPC level
437                         JSONArray metrics = sceneResult.getJSONArray("mpc_metrics");
438                         for (int i = 0; i < metrics.length(); i++) {
439                             String mpcResult = metrics.getString(i);
440                             try {
441                                 if (!matchMpcResult(cameraId, mpcResult)) {
442                                     Log.e(TAG, "Error parsing MPC result string:" + mpcResult);
443                                 }
444                             } catch (Exception e) {
445                                 Log.e(TAG, "Error parsing MPC result string:" + mpcResult, e);
446                             }
447 
448                         }
449 
450                         if (sceneResult.isNull("performance_metrics")) {
451                             continue;
452                         }
453 
454                         // Update performance metrics with metrics data from all
455                         // scenes for each camera
456                         JSONArray mArr = sceneResult.getJSONArray("performance_metrics");
457                         for (int i = 0; i < mArr.length(); i++) {
458                             String perfResult = mArr.getString(i);
459                             try {
460                                 if (!matchPerfMetricsResult(perfResult, camJsonObj)) {
461                                     Log.e(TAG, "Error parsing perf result string:" + perfResult);
462                                 }
463                             } catch (Exception e) {
464                                 Log.e(TAG, "Error parsing perf result string:" + perfResult, e);
465                             }
466                         }
467                     }
468                     // Add performance metrics for all scenes along with camera_id as json arr
469                     // to CtsVerifierReportLog for each camera.
470                     mFinalPerfMetricsArr.put(camJsonObj);
471                     mReportLog.addValues("perf_metrics", mFinalPerfMetricsArr);
472                 } catch (org.json.JSONException e) {
473                     Log.e(TAG, "Error reading json result string:" + results , e);
474                     return;
475                 }
476 
477                 // Submitting the report log generates a CtsCameraITSTestCases.reportlog.json
478                 // on device at path /sdcard/ReportLogFiles
479                 mReportLog.submit();
480 
481                 // Set summary if all scenes reported
482                 if (mSummaryMap.keySet().containsAll(mAllScenes)) {
483                     // Save test summary
484                     StringBuilder summary = new StringBuilder();
485                     for (String path : mSummaryMap.values()) {
486                         appendFileContentToSummary(summary, path);
487                     }
488                     if (summary.length() > MAX_SUMMARY_LEN) {
489                         Log.w(TAG, "ITS summary report too long: len: " + summary.length());
490                     }
491                     ItsTestActivity.this.getReportLog().setSummary(
492                             summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
493                 }
494 
495                 // Display current progress
496                 StringBuilder progress = new StringBuilder();
497                 for (ResultKey k : mAllScenes) {
498                     String status = RESULT_NOT_EXECUTED;
499                     if (mExecutedScenes.containsKey(k)) {
500                         status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL;
501                     }
502                     progress.append(String.format("Cam %s, %s: %s\n",
503                             k.cameraId, k.sceneId, status));
504                 }
505                 TextView progressView = (TextView) findViewById(R.id.its_progress);
506                 progressView.setMovementMethod(new ScrollingMovementMethod());
507                 progressView.setText(progress.toString());
508 
509 
510                 // Enable pass button if all scenes pass
511                 boolean allScenesPassed = true;
512                 for (ResultKey k : mAllScenes) {
513                     Boolean pass = mExecutedScenes.get(k);
514                     if (pass == null || pass == false) {
515                         allScenesPassed = false;
516                         break;
517                     }
518                 }
519                 if (allScenesPassed) {
520                     Log.i(TAG, "All scenes passed.");
521                     // Enable pass button
522                     ItsTestActivity.this.getPassButton().setEnabled(true);
523                     ItsTestActivity.this.setTestResultAndFinish(true);
524                 } else {
525                     ItsTestActivity.this.getPassButton().setEnabled(false);
526                 }
527             }
528         }
529 
appendFileContentToSummary(StringBuilder summary, String path)530         private void appendFileContentToSummary(StringBuilder summary, String path) {
531             BufferedReader reader = null;
532             try {
533                 reader = new BufferedReader(new FileReader(path));
534                 String line = null;
535                 do {
536                     line = reader.readLine();
537                     if (line != null) {
538                         summary.append(line);
539                     }
540                 } while (line != null);
541             } catch (FileNotFoundException e) {
542                 Log.e(TAG, "Cannot find ITS summary file at " + path);
543                 summary.append("Cannot find ITS summary file at " + path);
544             } catch (IOException e) {
545                 Log.e(TAG, "IO exception when trying to read " + path);
546                 summary.append("IO exception when trying to read " + path);
547             } finally {
548                 if (reader != null) {
549                     try {
550                         reader.close();
551                     } catch (IOException e) {
552                     }
553                 }
554             }
555         }
556 
matchMpcResult(String cameraId, String mpcResult)557         private boolean matchMpcResult(String cameraId, String mpcResult) {
558             Matcher launchMatcher = MPC12_CAMERA_LAUNCH_PATTERN.matcher(mpcResult);
559             boolean launchMatches = launchMatcher.matches();
560 
561             Matcher jpegMatcher = MPC12_JPEG_CAPTURE_PATTERN.matcher(mpcResult);
562             boolean jpegMatches = jpegMatcher.matches();
563 
564             Matcher gainmapMatcher = MPC15_ULTRA_HDR_PATTERN.matcher(mpcResult);
565             boolean gainmapMatches = gainmapMatcher.matches();
566             Log.i(TAG, "mpcResult: " + mpcResult);
567 
568             if (!launchMatches && !jpegMatches && !gainmapMatches) {
569                 return false;
570             }
571             if (!cameraId.equals(mPrimaryRearCameraId) &&
572                     !cameraId.equals(mPrimaryFrontCameraId)) {
573                 return false;
574             }
575 
576             if (launchMatches) {
577                 float latency = Float.parseFloat(launchMatcher.group(1));
578                 if (cameraId.equals(mPrimaryRearCameraId)) {
579                     mLaunchLatencyReq.setRearCameraLatency(latency);
580                 } else {
581                     mLaunchLatencyReq.setFrontCameraLatency(latency);
582                 }
583                 mExecutedMpcTests.add(new ResultKey(cameraId, MPC_LAUNCH_REQ_NUM));
584             } else if (jpegMatches) {
585                 float latency = Float.parseFloat(jpegMatcher.group(1));
586                 if (cameraId.equals(mPrimaryRearCameraId)) {
587                     mJpegLatencyReq.setRearCameraLatency(latency);
588                 } else {
589                     mJpegLatencyReq.setFrontCameraLatency(latency);
590                 }
591                 mExecutedMpcTests.add(new ResultKey(cameraId, MPC_JPEG_CAPTURE_REQ_NUM));
592             } else {
593                 Log.i(TAG, "Gainmap pattern matches");
594                 String result = mpcResult.split(":")[1];
595                 boolean hasGainMap = false;
596                 if (result.equals("true")) {
597                     hasGainMap = true;
598                 }
599                 if (cameraId.equals(mPrimaryRearCameraId)) {
600                     mUltraHdrReq.setRearCameraUltraHdrSupported(hasGainMap);
601                 } else {
602                     mUltraHdrReq.setFrontCameraUltraHdrSupported(hasGainMap);
603                 }
604                 mExecutedMpcTests.add(new ResultKey(cameraId, MPC_ULTRA_HDR_REQ_NUM));
605             }
606 
607             // Save MPC info once both front primary and rear primary data are collected.
608             if (mExecutedMpcTests.size() == 4) {
609                 mPce.submitAndVerify();
610             }
611             return true;
612         }
613 
parsePerfMetrics(String perfMetricsResult, JSONObject obj, List<String> floatKeys, List<String> booleanKeys, List<String> integerKeys)614         private void parsePerfMetrics(String perfMetricsResult, JSONObject obj,
615                 List<String> floatKeys, List<String> booleanKeys, List<String> integerKeys)
616                 throws org.json.JSONException {
617             String result = perfMetricsResult.replaceFirst(TEST_PATTERN, "");
618             String resultKey = result.split(":")[0].strip();
619             String strValue = result.split(":")[1].strip();
620 
621             if (strValue.equalsIgnoreCase("None")) {
622                 obj.put(resultKey, strValue);
623             } else if (floatKeys.stream().anyMatch(resultKey::contains)) {
624                 float value = Float.parseFloat(strValue);
625                 obj.put(resultKey, value);
626             } else if (booleanKeys.stream().anyMatch(resultKey::contains)) {
627                 boolean value = Boolean.parseBoolean(strValue);
628                 obj.put(resultKey, value);
629             } else if (integerKeys.stream().anyMatch(resultKey::contains)) {
630                 int value = Integer.parseInt(strValue);
631                 obj.put(resultKey, value);
632             } else {
633                 obj.put(resultKey, strValue);
634             }
635         }
636 
matchPerfMetricsResult(String perfMetricsResult, JSONObject obj)637         private boolean matchPerfMetricsResult(String perfMetricsResult, JSONObject obj) {
638             Matcher yuvPlusJpegMetricsMatcher = PERF_METRICS_YUV_PLUS_JPEG_PATTERN.matcher(
639                         perfMetricsResult);
640             boolean yuvPlusJpegMetricsMatches = yuvPlusJpegMetricsMatcher.matches();
641 
642             Matcher yuvPlusRawMetricsMatcher = PERF_METRICS_YUV_PLUS_RAW_PATTERN.matcher(
643                         perfMetricsResult);
644             boolean yuvPlusRawMetricsMatches = yuvPlusRawMetricsMatcher.matches();
645 
646             Matcher imuDriftMetricsMatcher = PERF_METRICS_IMU_DRIFT_PATTERN.matcher(
647                         perfMetricsResult);
648             boolean imuDriftMetricsMatches = imuDriftMetricsMatcher.matches();
649 
650             Matcher sensorFusionMetricsMatcher = PERF_METRICS_SENSOR_FUSION_PATTERN.matcher(
651                         perfMetricsResult);
652             boolean sensorFusionMetricsMatches = sensorFusionMetricsMatcher.matches();
653 
654             Matcher burstCaptureMetricsMatcher = PERF_METRICS_BURST_CAPTURE_PATTERN.matcher(
655                         perfMetricsResult);
656             boolean burstCaptureMetricsMatches = burstCaptureMetricsMatcher.matches();
657 
658             Matcher distortionMetricsMatcher = PERF_METRICS_DISTORTION_PATTERN.matcher(
659                     perfMetricsResult);
660             boolean distortionMetricsMatches = distortionMetricsMatcher.matches();
661 
662             Matcher intrinsicMetricsMatcher = PERF_METRICS_INTRINSIC_PATTERN.matcher(
663                     perfMetricsResult);
664             boolean intrinsicMetricsMatches = intrinsicMetricsMatcher.matches();
665 
666             Matcher lowLightBoostMetricsMatcher =
667                     PERF_METRICS_LOW_LIGHT_BOOST_PATTERN.matcher(perfMetricsResult);
668             boolean lowLightBoostMetricsMatches = lowLightBoostMetricsMatcher.matches();
669 
670             Matcher nightModeExtensionMetricsMatcher =
671                     PERF_METRICS_EXTENSION_NIGHT_MODE_PATTERN.matcher(perfMetricsResult);
672             boolean nightModeExtensionMetricsMatches = nightModeExtensionMetricsMatcher.matches();
673 
674             Matcher aeAwbMetricsMatcher = PERF_METRICS_AEAWB_PATTERN.matcher(
675                     perfMetricsResult);
676             boolean aeAwbMetricsMatches = aeAwbMetricsMatcher.matches();
677 
678             Matcher multiCamMetricsMatcher = PERF_METRICS_MULTICAM_PATTERN.matcher(
679                     perfMetricsResult);
680             boolean multiCamMetricsMatches = multiCamMetricsMatcher.matches();
681 
682             Matcher previewFrameDropMetricsMatcher =
683                     PERF_METRICS_PREVIEW_FRAME_DROP_PATTERN.matcher(perfMetricsResult);
684             boolean previewFrameDropMetricsMatches = previewFrameDropMetricsMatcher.matches();
685 
686 
687             if (!yuvPlusJpegMetricsMatches && !yuvPlusRawMetricsMatches
688                         && !imuDriftMetricsMatches && !sensorFusionMetricsMatches
689                         && !burstCaptureMetricsMatches && !distortionMetricsMatches
690                         && !intrinsicMetricsMatches && !lowLightBoostMetricsMatches
691                         && !nightModeExtensionMetricsMatches && !aeAwbMetricsMatches
692                         && !multiCamMetricsMatches && !previewFrameDropMetricsMatches) {
693                 return false;
694             }
695 
696             try {
697                 if (yuvPlusJpegMetricsMatches) {
698                     Log.i(TAG, "jpeg pattern  matches");
699                     float diff = Float.parseFloat(yuvPlusJpegMetricsMatcher.group(1));
700                     obj.put("yuv_plus_jpeg_rms_diff", diff);
701                 }
702 
703                 if (yuvPlusRawMetricsMatches) {
704                     Log.i(TAG, "yuv plus raw pattern matches");
705                     addPerfMetricsResult(PERF_METRICS_KEY_PREFIX_YUV_PLUS, perfMetricsResult, obj);
706                 }
707 
708                 if (imuDriftMetricsMatches) {
709                     Log.i(TAG, "imu drift matches");
710                     // remove "test_" from the result
711                     String result = perfMetricsResult.replaceFirst(TEST_PATTERN, "");
712                     String resultKey = result.split(":")[0].strip();
713                     if (resultKey.contains("seconds") || resultKey.contains("hz")) {
714                         float value = Float.parseFloat(result.split(":")[1].strip());
715                         obj.put(resultKey, value);
716                     } else {
717                         String value = result.split(":")[1].strip();
718                         obj.put(resultKey, value);
719                     }
720                 }
721 
722                 if (sensorFusionMetricsMatches) {
723                     Log.i(TAG, "sensor fusion matches");
724                     addPerfMetricsResult(PERF_METRICS_KEY_PREFIX_SENSOR_FUSION, perfMetricsResult,
725                             obj);
726                 }
727 
728                 if (burstCaptureMetricsMatches) {
729                     Log.i(TAG, "burst capture matches");
730                     addPerfMetricsResult(PERF_METRICS_KEY_PREFIX_BURST_CAPTURE, perfMetricsResult,
731                             obj);
732                 }
733 
734                 if (distortionMetricsMatches) {
735                     List<String> floatKeys = Arrays.asList(ZOOM, "distortion_error",
736                             "chart_coverage");
737                     List<String> integerKeys = Arrays.asList("physical_id");
738                     parsePerfMetrics(perfMetricsResult, obj, floatKeys, Collections.emptyList(),
739                             integerKeys);
740                 }
741                 if (intrinsicMetricsMatches) {
742                     List<String> floatKeys = Arrays.asList("max_principal_point_diff");
743                     List<String> booleanKeys = Arrays.asList(
744                             "samples_principal_points_diff_detected");
745                     parsePerfMetrics(perfMetricsResult, obj, floatKeys, booleanKeys,
746                             Collections.emptyList());
747                 }
748                 if (aeAwbMetricsMatches) {
749                     List<String> floatKeys = Arrays.asList("_change");
750                     parsePerfMetrics(perfMetricsResult, obj, floatKeys, Collections.emptyList(),
751                             Collections.emptyList());
752                 }
753 
754                 if (lowLightBoostMetricsMatches) {
755                     Log.i(TAG, "low light boost matches");
756                     addPerfMetricsResult(PERF_METRICS_KEY_PREFIX_LOW_LIGHT, perfMetricsResult, obj);
757                 }
758 
759                 if (nightModeExtensionMetricsMatches) {
760                     Log.i(TAG, "night mode extension matches");
761                     addPerfMetricsResult(PERF_METRICS_KEY_PREFIX_NIGHT, perfMetricsResult, obj);
762                 }
763 
764                 if (multiCamMetricsMatches) {
765                     Log.i(TAG, "multi cam metrics matches");
766                     addMultiCamPerfMetricsResult(perfMetricsResult, obj);
767                 }
768 
769                 if (previewFrameDropMetricsMatches) {
770                     Log.i(TAG, "preview frame drop matches");
771                     addPerfMetricsResult(PERF_METRICS_KEY_PREFIX_PREVIEW_FRAME_DROP,
772                             perfMetricsResult, obj);
773                 }
774             } catch (org.json.JSONException e) {
775                 Log.e(TAG, "Error when serializing the metrics into a JSONObject", e);
776             }
777 
778             return true;
779         }
780     }
781 
addMultiCamPerfMetricsResult(String perfMetricsResult, JSONObject obj)782     private void addMultiCamPerfMetricsResult(String perfMetricsResult,
783             JSONObject obj) throws org.json.JSONException {
784         String[] parts = perfMetricsResult.split(":", 2); // Limit to 2 to avoid splitting values
785         if (parts.length == 2) {
786             String key = parts[0].trim().replaceFirst(TEST_PATTERN, "");
787             String value = parts[1].trim();
788             Log.i(TAG, "Key: " + key);
789             Log.i(TAG, "Value: " + value);
790             obj.put(key, value);
791         } else {
792             Log.i(TAG, "Invalid output string");
793         }
794     }
795 
796     /* TODO b/346817862 - Move logic to regex as string splits and trims are brittle. */
addPerfMetricsResult(String keyPrefix, String perfMetricsResult, JSONObject obj)797     private void addPerfMetricsResult(String keyPrefix, String perfMetricsResult,
798             JSONObject obj) throws org.json.JSONException {
799         // remove "test_" from the result
800         String result = perfMetricsResult.replaceFirst("^test_", "");
801         String resultKey = result.split(":")[0].strip();
802         String value = result.split(":")[1].strip();
803         if (resultKey.contains(PERF_METRICS_KEY_CHART_LUMA)) {
804             int[] chartLumaValues = Arrays.stream(value.substring(1, value.length() - 1)
805                     .split(","))
806                     .map(String::trim)
807                     .mapToInt(Integer::parseInt)
808                     .toArray();
809             JSONArray chartLumaValuesJson = new JSONArray();
810             for (int luma : chartLumaValues) {
811                 chartLumaValuesJson.put(luma);
812             }
813             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_CHART_LUMA, chartLumaValuesJson);
814         } else if (resultKey.contains(PERF_METRICS_KEY_DELTA_AVG_LUMA)) {
815             BigDecimal floatValue = new BigDecimal(value);
816             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_DELTA_AVG_LUMA, floatValue);
817         } else if (resultKey.contains(PERF_METRICS_KEY_AVG_LUMA)) {
818             BigDecimal floatValue = new BigDecimal(value);
819             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_AVG_LUMA, floatValue);
820         } else if (resultKey.contains(PERF_METRICS_KEY_RAW)) {
821             BigDecimal floatValue = new BigDecimal(value);
822             obj.put(keyPrefix + PERF_METRICS_KEY_RAW + PERF_METRICS_KEY_RMS_DIFF, floatValue);
823         } else if (resultKey.contains(PERF_METRICS_KEY_RAW10)) {
824             BigDecimal floatValue = new BigDecimal(value);
825             obj.put(keyPrefix + PERF_METRICS_KEY_RAW10 + "_" + PERF_METRICS_KEY_RMS_DIFF,
826                     floatValue);
827         } else if (resultKey.contains(PERF_METRICS_KEY_RAW12)) {
828             BigDecimal floatValue = new BigDecimal(value);
829             obj.put(keyPrefix + PERF_METRICS_KEY_RAW12 + "_" + PERF_METRICS_KEY_RMS_DIFF,
830                     floatValue);
831         } else if (resultKey.contains(PERF_METRICS_KEY_PREFIX_BURST_CAPTURE)) {
832             BigDecimal floatValue = new BigDecimal(value);
833             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_FRAMEDURATION, floatValue);
834         } else if (resultKey.contains(PERF_METRICS_KEY_CORR_DIST)) {
835             BigDecimal floatValue = new BigDecimal(value);
836             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_CORR_DIST, floatValue);
837         } else if (resultKey.contains(PERF_METRICS_KEY_OFFSET_MS)) {
838             BigDecimal floatValue = new BigDecimal(value);
839             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_OFFSET_MS, floatValue);
840         } else if (resultKey.contains(PERF_METRICS_KEY_MAX_DELTA)) {
841             BigDecimal floatValue = new BigDecimal(value);
842             obj.put(keyPrefix + "_" + PERF_METRICS_KEY_MAX_DELTA, floatValue);
843         }
844     }
845 
846     private class FoldStateListener implements
847             DeviceStateManager.DeviceStateCallback {
848         private int[] mFoldedDeviceStates;
849         private boolean mFirstFoldCheck = false;
850 
FoldStateListener(Context context)851         FoldStateListener(Context context) {
852             Resources systemRes = Resources.getSystem();
853             int foldedStatesArrayIdentifier = systemRes.getIdentifier("config_foldedDeviceStates",
854                     "array", "android");
855             mFoldedDeviceStates = systemRes.getIntArray(foldedStatesArrayIdentifier);
856         }
857 
858         @Override
onDeviceStateChanged(DeviceState state)859         public final void onDeviceStateChanged(DeviceState state) {
860             int stateIdentifier = state.getIdentifier();
861             boolean folded = ArrayUtils.contains(mFoldedDeviceStates, stateIdentifier);
862             Log.i(TAG, "Is device folded? " + mIsDeviceFolded);
863             if (!mFirstFoldCheck || mIsDeviceFolded != folded) {
864                 mIsDeviceFolded = folded;
865                 mFirstFoldCheck = true;
866                 if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
867                     Log.i(TAG, "Setup is done for both the states.");
868                 } else {
869                     runOnUiThread(new Runnable() {
870                         @Override
871                         public void run() {
872                             Log.i(TAG, "set up from onStateChanged");
873                             getCameraIdsForFoldableDevice();
874                             setupItsTestsForFoldableDevice(mAdapter);
875                         }
876                     });
877                 }
878             } else {
879                 Log.i(TAG, "Last state is same as new state.");
880             }
881         }
882     }
883 
884     @Override
onCreate(Bundle savedInstanceState)885     protected void onCreate(Bundle savedInstanceState) {
886         // Hide the test if all camera devices are legacy
887         mCameraManager = this.getSystemService(CameraManager.class);
888         if (mReportLog == null) {
889             mReportLog =
890                     new CtsVerifierReportLog(REPORT_LOG_NAME, "camera_its_results");
891         }
892         Context context = this.getApplicationContext();
893         if (mAllScenes == null) {
894             mAllScenes = new TreeSet<>(mComparator);
895         }
896         if (mExecutedMpcTests == null) {
897             mExecutedMpcTests = new TreeSet<>(mComparator);
898         }
899         mCameraThread = new HandlerThread("ItsTestActivityThread");
900         mCameraThread.start();
901         mCameraHandler = new Handler(mCameraThread.getLooper());
902         HandlerExecutor handlerExecutor = new HandlerExecutor(mCameraHandler);
903         // mIsFoldableDevice is set True for foldables to listen to callback
904         // in FoldStateListener
905         mIsFoldableDevice = isFoldableDevice();
906         Log.i(TAG, "Is device foldable? " + mIsFoldableDevice);
907         if (mIsFoldableDevice) {
908             FoldStateListener foldStateListener = new FoldStateListener(context);
909             mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
910             // onStateChanged will be called upon registration which helps determine
911             // if the foldable device has changed the folded/unfolded state or not.
912             mDeviceStateManager.registerCallback(handlerExecutor, foldStateListener);
913         }
914         if (!mIsFoldableDevice) {
915             try {
916                 ItsUtils.ItsCameraIdList cameraIdList =
917                         ItsUtils.getItsCompatibleCameraIds(mCameraManager);
918                 mToBeTestedCameraIds = cameraIdList.mCameraIdCombos;
919                 mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
920                 mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
921             } catch (ItsException e) {
922                 Toast.makeText(ItsTestActivity.this,
923                         "Received error from camera service while checking device capabilities: "
924                                 + e, Toast.LENGTH_SHORT).show();
925             }
926         }
927 
928         super.onCreate(savedInstanceState);
929 
930         if (!mIsFoldableDevice) {
931             if (mToBeTestedCameraIds.size() == 0) {
932                 showToast(R.string.all_exempted_devices);
933                 ItsTestActivity.this.getReportLog().setSummary(
934                         "PASS: all cameras on this device are exempted from ITS",
935                         1.0, ResultType.NEUTRAL, ResultUnit.NONE);
936                 setTestResultAndFinish(true);
937             }
938         }
939         // Default locale must be set to "en-us"
940         Locale locale = Locale.getDefault();
941         if (!Locale.US.equals(locale)) {
942             String toastMessage = "Unsupported default language " + locale + "! "
943                     + "Please switch the default language to English (United States) in "
944                     + "Settings > Language & input > Languages";
945             Toast.makeText(ItsTestActivity.this, toastMessage, Toast.LENGTH_LONG).show();
946             ItsTestActivity.this.getReportLog().setSummary(
947                     "FAIL: Default language is not set to " + Locale.US,
948                     1.0, ResultType.NEUTRAL, ResultUnit.NONE);
949             setTestResultAndFinish(false);
950         }
951         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
952     }
953 
getCameraIdsAvailableForTesting()954     private List<String> getCameraIdsAvailableForTesting() {
955         List<String> toBeTestedCameraIds = new ArrayList<String>();
956         List<String> availableCameraIdList = new ArrayList<String>();
957         try {
958             ItsUtils.ItsCameraIdList cameraIdList =
959                     ItsUtils.getItsCompatibleCameraIds(mCameraManager);
960             toBeTestedCameraIds = cameraIdList.mCameraIdCombos;
961             mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
962             mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
963             mUnavailablePhysicalCameras = getUnavailablePhysicalCameras();
964             Log.i(TAG, "unavailablePhysicalCameras:"
965                     + mUnavailablePhysicalCameras.toString());
966             for (String str : toBeTestedCameraIds) {
967                 if (str.contains(".")) {
968                     String[] strArr = str.split("\\.");
969                     if (mUnavailablePhysicalCameras.contains(new Pair<>(strArr[0], strArr[1]))) {
970                         toBeTestedCameraIds.remove(str);
971                     }
972                 }
973             }
974             Log.i(TAG, "AvailablePhysicalCameras to be tested:"
975                     + Arrays.asList(toBeTestedCameraIds.toString()));
976         } catch (ItsException e) {
977             Log.i(TAG, "Received error from camera service while checking device capabilities: "
978                     + e);
979         } catch (Exception e) {
980             Log.i(TAG, "Exception: " + e);
981         }
982 
983         return toBeTestedCameraIds;
984     }
985 
986     // Get camera ids available for testing for device in
987     // each state: folded and unfolded.
getCameraIdsForFoldableDevice()988     protected void getCameraIdsForFoldableDevice() {
989         boolean deviceFolded = mIsDeviceFolded;
990         try {
991             if (mIsDeviceFolded) {
992                 mToBeTestedCameraIdsFolded = getCameraIdsAvailableForTesting();
993             } else {
994                 mToBeTestedCameraIdsUnfolded = getCameraIdsAvailableForTesting();
995             }
996         } catch (Exception e) {
997             Log.i(TAG, "Exception: " + e);
998         }
999     }
1000 
1001     @Override
showManualTestDialog(final DialogTestListItem test, final DialogTestListItem.TestCallback callback)1002     public void showManualTestDialog(final DialogTestListItem test,
1003             final DialogTestListItem.TestCallback callback) {
1004         //Nothing todo for ITS
1005     }
1006 
testTitle(String cam, String scene)1007     protected String testTitle(String cam, String scene) {
1008         return "Camera: " + cam + ", " + scene;
1009     }
1010 
1011     // CtsVerifier has a "Folded" toggle that selectively surfaces some tests.
1012     // To separate the tests in folded and unfolded states, CtsVerifier adds a [folded]
1013     // suffix to the test id in its internal database depending on the state of the "Folded"
1014     // toggle button. However, CameraITS has tests that it needs to persist across both folded
1015     // and unfolded states.To get the test results to persist, we need CtsVerifier to store and
1016     // look up the same test id regardless of the toggle button state.
1017     // TODO(b/282804139): Update CTS tests to allow activities to write tests that persist
1018     // across the states
testId(String cam, String scene)1019     protected String testId(String cam, String scene) {
1020         return "Camera_ITS_" + cam + "_" + scene + "[folded]";
1021     }
1022 
isFoldableDevice()1023     protected boolean isFoldableDevice() {
1024         Context context = this.getApplicationContext();
1025         return CameraUtils.isDeviceFoldable(context);
1026     }
1027 
isDeviceFolded()1028     protected boolean isDeviceFolded() {
1029         return mIsDeviceFolded;
1030     }
1031 
getUnavailablePhysicalCameras()1032     protected Set<Pair<String, String>> getUnavailablePhysicalCameras() throws ItsException {
1033         final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
1034                 new LinkedBlockingQueue<>();
1035         mCameraThread = new HandlerThread("ItsCameraThread");
1036         mCameraThread.start();
1037         mCameraHandler = new Handler(mCameraThread.getLooper());
1038         try {
1039             CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
1040                 @Override
1041                 public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
1042                     unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
1043                 }
1044             };
1045             mCameraManager.registerAvailabilityCallback(ac, mCameraHandler);
1046             Set<Pair<String, String>> unavailablePhysicalCameras =
1047                     new HashSet<Pair<String, String>>();
1048             Pair<String, String> candidatePhysicalIds =
1049                     unavailablePhysicalCamEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
1050                     java.util.concurrent.TimeUnit.MILLISECONDS);
1051             while (candidatePhysicalIds != null) {
1052                 unavailablePhysicalCameras.add(candidatePhysicalIds);
1053                 candidatePhysicalIds =
1054                         unavailablePhysicalCamEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
1055                         java.util.concurrent.TimeUnit.MILLISECONDS);
1056             }
1057             mCameraManager.unregisterAvailabilityCallback(ac);
1058             return unavailablePhysicalCameras;
1059         } catch (Exception e) {
1060             throw new ItsException("Exception: ", e);
1061         }
1062     }
1063 
setupItsTests(ArrayTestListAdapter adapter)1064     protected void setupItsTests(ArrayTestListAdapter adapter) {
1065         for (String cam : mToBeTestedCameraIds) {
1066             List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER)
1067                     ? mHiddenPhysicalCameraSceneIds : mSceneIds;
1068             for (String scene : scenes) {
1069                 // Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
1070                 // devices with sub-cameras.
1071                 mAllScenes.add(new ResultKey(cam, scene));
1072                 adapter.add(new DialogTestListItem(this,
1073                         testTitle(cam, scene),
1074                         testId(cam, scene)));
1075             }
1076             if (mExecutedMpcTests == null) {
1077                 mExecutedMpcTests = new TreeSet<>(mComparator);
1078             }
1079             Log.d(TAG, "Total combinations to test on this device:" + mAllScenes.size());
1080         }
1081     }
1082 
setupItsTestsForFoldableDevice(ArrayTestListAdapter adapter)1083     protected void setupItsTestsForFoldableDevice(ArrayTestListAdapter adapter) {
1084         List<String> toBeTestedCameraIds = new ArrayList<String>();
1085         if (mIsDeviceFolded) {
1086             toBeTestedCameraIds = mToBeTestedCameraIdsFolded;
1087         } else {
1088             toBeTestedCameraIds = mToBeTestedCameraIdsUnfolded;
1089         }
1090 
1091         for (String cam : toBeTestedCameraIds) {
1092             List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER)
1093                     ? mHiddenPhysicalCameraSceneIds : mSceneIds;
1094             for (String scene : scenes) {
1095                 // Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
1096                 // devices with sub-cameras.
1097                 if (cam.contains(mPrimaryFrontCameraId) && mIsDeviceFolded) {
1098                     scene = scene + "_folded";
1099                 }
1100                 // Rear camera scenes will be added only once.
1101                 if (mAllScenes.contains(new ResultKey(cam, scene))) {
1102                     continue;
1103                 }
1104                 // TODO(ruchamk): Remove extra logging after testing.
1105                 Log.i(TAG, "Adding cam_id: " + cam + "scene: " + scene);
1106                 mAllScenes.add(new ResultKey(cam, scene));
1107                 adapter.add(new DialogTestListItem(this,
1108                         testTitle(cam, scene),
1109                         testId(cam, scene)));
1110             }
1111         }
1112         Log.d(TAG, "Total combinations to test on this device:"
1113                 + mAllScenes.size() + " folded? " + mIsDeviceFolded);
1114         if (mIsDeviceFolded) {
1115             mFoldedTestSetupDone = true;
1116             Log.i(TAG, "mFoldedTestSetupDone");
1117         } else {
1118             mUnfoldedTestSetupDone = true;
1119             Log.i(TAG, "mUnfoldedTestSetupDone");
1120         }
1121         if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
1122             Log.d(TAG, "Total combinations to test on this foldable "
1123                     + "device for both states:" + mAllScenes.size());
1124         }
1125         adapter.loadTestResults();
1126     }
1127 
1128     @Override
setupTests(ArrayTestListAdapter adapter)1129     protected void setupTests(ArrayTestListAdapter adapter) {
1130         mAdapter = adapter;
1131         if (mIsFoldableDevice) {
1132             if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
1133                 Log.i(TAG, "Set up is done");
1134             }
1135         } else {
1136             setupItsTests(adapter);
1137         }
1138     }
1139 
1140     @Override
onResume()1141     protected void onResume() {
1142         super.onResume();
1143         if (mCameraManager == null) {
1144             showToast(R.string.no_camera_manager);
1145         } else {
1146             Log.d(TAG, "register ITS result receiver and command receiver");
1147             IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
1148             registerReceiver(mResultsReceiver, filter, Context.RECEIVER_EXPORTED);
1149             filter = new IntentFilter(ACTION_ITS_DO_JCA_CAPTURE);
1150             registerReceiver(mCommandReceiver, filter, Context.RECEIVER_EXPORTED);
1151             mReceiverRegistered = true;
1152         }
1153     }
1154 
1155     @Override
onDestroy()1156     public void onDestroy() {
1157         Log.d(TAG, "unregister ITS result receiver");
1158         if (mReceiverRegistered) {
1159             unregisterReceiver(mResultsReceiver);
1160             unregisterReceiver(mCommandReceiver);
1161         }
1162         super.onDestroy();
1163     }
1164 
1165     @Override
onConfigurationChanged(Configuration newConfig)1166     public void onConfigurationChanged(Configuration newConfig) {
1167         super.onConfigurationChanged(newConfig);
1168         setContentView(R.layout.its_main);
1169         setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
1170         setPassFailButtonClickListeners();
1171         // Changing folded state can incorrectly enable pass button
1172         ItsTestActivity.this.getPassButton().setEnabled(false);
1173     }
1174 
1175     @Override
handleActivityResult(int requestCode, int resultCode, Intent data)1176     public void handleActivityResult(int requestCode, int resultCode, Intent data) {
1177         Logt.i(TAG, "request code: " + requestCode + ", result code: " + resultCode);
1178         if (requestCode == REQUEST_IMAGE_CAPTURE) {
1179             if (resultCode != RESULT_OK) {
1180                 Logt.e(TAG, "Capture failed!");
1181             }
1182             Intent serviceIntent = new Intent(this, ItsService.class);
1183             serviceIntent.putExtra(JCA_CAPTURE_PATH_TAG, mJcaCapturePath);
1184             serviceIntent.putExtra(JCA_CAPTURE_STATUS_TAG, resultCode);
1185             startService(serviceIntent);
1186         } else {
1187             super.handleActivityResult(requestCode, resultCode, data);
1188         }
1189     }
1190 
doJcaCapture()1191     private void doJcaCapture() {
1192         Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
1193         File imageDir = new File(this.getExternalFilesDir(null), JCA_FILES_CHILD_PATHNAME);
1194         imageDir.mkdirs();
1195         if (!imageDir.exists()) {
1196             Logt.e(TAG, "Could not create image directory");
1197             return;
1198         }
1199         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")
1200                 .withZone(ZoneId.systemDefault());
1201         String timestamp = formatter.format(Instant.now());
1202         File imageFile = new File(imageDir, "ITS_JCA_" + timestamp + ".jpg");
1203         Logt.i(TAG, "file path: " + imageFile.toString());
1204         mJcaCapturePath = imageFile.toString();
1205         Uri photoUri = FileProvider.getUriForFile(
1206                 this,
1207                 "com.android.cts.verifier.managedprovisioning.fileprovider",
1208                 imageFile);
1209         takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
1210         takePictureIntent.setComponent(new ComponentName(
1211                 JCA_PACKAGE_NAME, JCA_PACKAGE_NAME + "." + JCA_ACTIVITY_NAME));
1212         takePictureIntent.setFlags(
1213                 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
1214         try {
1215             startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
1216         } catch (ActivityNotFoundException e) {
1217             Logt.e(TAG, "Error starting image capture intent activity: " + e);
1218         }
1219     }
1220 }
1221