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