• 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.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.hardware.camera2.CameraManager;
26 import android.hardware.cts.helpers.CameraUtils;
27 import android.hardware.devicestate.DeviceStateManager;
28 import android.mediapc.cts.common.PerformanceClassEvaluator;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.text.method.ScrollingMovementMethod;
33 import android.util.Log;
34 import android.util.Pair;
35 import android.view.WindowManager;
36 import android.widget.TextView;
37 import android.widget.Toast;
38 
39 import com.android.compatibility.common.util.ResultType;
40 import com.android.compatibility.common.util.ResultUnit;
41 import com.android.cts.verifier.ArrayTestListAdapter;
42 import com.android.cts.verifier.DialogTestListActivity;
43 import com.android.cts.verifier.R;
44 import com.android.cts.verifier.TestResult;
45 import com.android.internal.util.ArrayUtils;
46 
47 import org.json.JSONArray;
48 import org.json.JSONObject;
49 import org.junit.rules.TestName;
50 
51 import java.io.BufferedReader;
52 import java.io.FileNotFoundException;
53 import java.io.FileReader;
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Comparator;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.List;
62 import java.util.Locale;
63 import java.util.Set;
64 import java.util.TreeSet;
65 import java.util.concurrent.Executor;
66 import java.util.concurrent.LinkedBlockingQueue;
67 import java.util.regex.Matcher;
68 import java.util.regex.Pattern;
69 
70 /**
71  * Test for Camera features that require that the camera be aimed at a specific test scene.
72  * This test activity requires a USB connection to a computer, and a corresponding host-side run of
73  * the python scripts found in the CameraITS directory.
74  */
75 public class ItsTestActivity extends DialogTestListActivity {
76     private static final String TAG = "ItsTestActivity";
77     private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
78     private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
79     private static final String EXTRA_VERSION = "camera.its.extra.VERSION";
80     private static final String CURRENT_VERSION = "1.0";
81     private static final String ACTION_ITS_RESULT =
82             "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
83 
84     private static final String RESULT_PASS = "PASS";
85     private static final String RESULT_FAIL = "FAIL";
86     private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED";
87     private static final Set<String> RESULT_VALUES = new HashSet<String>(
88             Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
89     private static final int MAX_SUMMARY_LEN = 200;
90 
91     private static final Pattern MPC12_CAMERA_LAUNCH_PATTERN =
92             Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
93     private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
94             Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
95     private static final int AVAILABILITY_TIMEOUT_MS = 10;
96 
97     private final ResultReceiver mResultsReceiver = new ResultReceiver();
98     private boolean mReceiverRegistered = false;
99 
100     public final TestName mTestName = new TestName();
101     private  boolean mIsFoldableDevice = false;
102     private  boolean mIsDeviceFolded = false;
103     private  boolean mFoldedTestSetupDone = false;
104     private  boolean mUnfoldedTestSetupDone = false;
105     private  Set<Pair<String, String>> mUnavailablePhysicalCameras =
106             new HashSet<Pair<String, String>>();
107     private CameraManager mCameraManager = null;
108     private DeviceStateManager mDeviceStateManager = null;
109     private HandlerThread mCameraThread = null;
110     private Handler mCameraHandler = null;
111 
112     // Initialized in onCreate
113     List<String> mToBeTestedCameraIds = null;
114     private  String mPrimaryRearCameraId = null;
115     private  String mPrimaryFrontCameraId = null;
116     private  List<String> mToBeTestedCameraIdsUnfolded = null;
117     private  List<String> mToBeTestedCameraIdsFolded = null;
118     private  String mPrimaryRearCameraIdUnfolded = null;
119     private  String mPrimaryFrontCameraIdUnfolded = null;
120     private ArrayTestListAdapter mAdapter;
121 
122     // Scenes
123     private static final List<String> mSceneIds = List.of(
124             "scene0",
125             "scene1_1",
126             "scene1_2",
127             "scene2_a",
128             "scene2_b",
129             "scene2_c",
130             "scene2_d",
131             "scene2_e",
132             "scene2_f",
133             "scene3",
134             "scene4",
135             "scene5",
136             "scene6",
137             "scene_extensions/scene_hdr",
138             "scene_extensions/scene_night",
139             "sensor_fusion");
140 
141     // This must match scenes of SUB_CAMERA_TESTS in tools/run_all_tests.py
142     private static final List<String> mHiddenPhysicalCameraSceneIds = List.of(
143             "scene0",
144             "scene1_1",
145             "scene1_2",
146             "scene2_a",
147             "scene4",
148             "sensor_fusion");
149 
150     // TODO: cache the following in saved bundle
151     private Set<ResultKey> mAllScenes = null;
152     // (camera, scene) -> (pass, fail)
153     private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
154     // map camera id to ITS summary report path
155     private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
156     // All primary cameras for which MPC level test has run
157     private Set<ResultKey> mExecutedMpcTests = null;
158     private static final String MPC_LAUNCH_REQ_NUM = "2.2.7.2/7.5/H-1-6";
159     private static final String MPC_JPEG_CAPTURE_REQ_NUM = "2.2.7.2/7.5/H-1-5";
160     // Performance class evaluator used for writing test result
161     PerformanceClassEvaluator mPce = new PerformanceClassEvaluator(mTestName);
162     PerformanceClassEvaluator.CameraLatencyRequirement mJpegLatencyReq =
163             mPce.addR7_5__H_1_5();
164     PerformanceClassEvaluator.CameraLatencyRequirement mLaunchLatencyReq =
165             mPce.addR7_5__H_1_6();
166 
167     private static class HandlerExecutor implements Executor {
168         private final Handler mHandler;
169 
HandlerExecutor(Handler handler)170         HandlerExecutor(Handler handler) {
171             mHandler = handler;
172         }
173 
174         @Override
execute(Runnable runCmd)175         public void execute(Runnable runCmd) {
176             mHandler.post(runCmd);
177         }
178     }
179 
180     final class ResultKey {
181         public final String cameraId;
182         public final String sceneId;
183 
ResultKey(String cameraId, String sceneId)184         public ResultKey(String cameraId, String sceneId) {
185             this.cameraId = cameraId;
186             this.sceneId = sceneId;
187         }
188 
189         @Override
equals(final Object o)190         public boolean equals(final Object o) {
191             if (o == null) return false;
192             if (this == o) return true;
193             if (o instanceof ResultKey) {
194                 final ResultKey other = (ResultKey) o;
195                 return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId);
196             }
197             return false;
198         }
199 
200         @Override
hashCode()201         public int hashCode() {
202             int h = cameraId.hashCode();
203             h = ((h << 5) - h) ^ sceneId.hashCode();
204             return h;
205         }
206     }
207 
ItsTestActivity()208     public ItsTestActivity() {
209         super(R.layout.its_main,
210                 R.string.camera_its_test,
211                 R.string.camera_its_test_info,
212                 R.string.camera_its_test);
213     }
214 
215     private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
216         @Override
217         public int compare(ResultKey k1, ResultKey k2) {
218             if (k1.cameraId.equals(k2.cameraId))
219                 return k1.sceneId.compareTo(k2.sceneId);
220             return k1.cameraId.compareTo(k2.cameraId);
221         }
222     };
223 
224     class ResultReceiver extends BroadcastReceiver {
225         @Override
onReceive(Context context, Intent intent)226         public void onReceive(Context context, Intent intent) {
227             Log.i(TAG, "Received result for Camera ITS tests");
228             if (ACTION_ITS_RESULT.equals(intent.getAction())) {
229                 String version = intent.getStringExtra(EXTRA_VERSION);
230                 if (version == null || !version.equals(CURRENT_VERSION)) {
231                     Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION +
232                             ", got " + ((version == null) ? "null" : version));
233                     ItsTestActivity.this.showToast(R.string.its_version_mismatch);
234                     return;
235                 }
236 
237                 String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
238                 String results = intent.getStringExtra(EXTRA_RESULTS);
239                 if (cameraId == null || results == null) {
240                     Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) +
241                             ", results = " + ((results == null) ? "null" : results));
242                     return;
243                 }
244 
245                 if (mIsFoldableDevice) {
246                     if (!mIsDeviceFolded) {
247                         if (!mToBeTestedCameraIdsUnfolded.contains(cameraId)) {
248                             Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
249                             return;
250                         }
251                     } else {
252                         if (!mToBeTestedCameraIdsFolded.contains(cameraId)) {
253                             Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
254                             return;
255                         }
256                     }
257                 } else {
258                     if (!mToBeTestedCameraIds.contains(cameraId)) {
259                         Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
260                         return;
261                     }
262                 }
263 
264                 try {
265                     /* Sample JSON results string
266                     {
267                        "scene0":{
268                           "result":"PASS",
269                           "summary":"/sdcard/cam0_scene0.txt"
270                        },
271                        "scene1":{
272                           "result":"NOT_EXECUTED"
273                        },
274                        "scene2":{
275                           "result":"FAIL",
276                           "summary":"/sdcard/cam0_scene2.txt"
277                        }
278                     }
279                     */
280                     JSONObject jsonResults = new JSONObject(results);
281                     Log.d(TAG,"Results received:" + jsonResults.toString());
282                     Set<String> scenes = new HashSet<>();
283                     Iterator<String> keys = jsonResults.keys();
284                     while (keys.hasNext()) {
285                         scenes.add(keys.next());
286                     }
287 
288                     // Update test execution results
289                     for (String scene : scenes) {
290                         JSONObject sceneResult = jsonResults.getJSONObject(scene);
291                         Log.v(TAG, sceneResult.toString());
292                         String result = sceneResult.getString("result");
293                         if (result == null) {
294                             Log.e(TAG, "Result for " + scene + " is null");
295                             return;
296                         }
297                         Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result);
298                         if (!RESULT_VALUES.contains(result)) {
299                             Log.e(TAG, "Unknown result for " + scene + ": " + result);
300                             return;
301                         }
302                         ResultKey key = new ResultKey(cameraId, scene);
303                         if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
304                             boolean pass = result.equals(RESULT_PASS);
305                             mExecutedScenes.put(key, pass);
306                             // Get start/end time per camera/scene for result history collection.
307                             mStartTime = sceneResult.getLong("start");
308                             mEndTime = sceneResult.getLong("end");
309                             setTestResult(testId(cameraId, scene), pass ?
310                                     TestResult.TEST_RESULT_PASSED : TestResult.TEST_RESULT_FAILED);
311                             Log.e(TAG, "setTestResult for " + testId(cameraId, scene) + ": " + result);
312                             String summary = sceneResult.optString("summary");
313                             if (!summary.equals("")) {
314                                 mSummaryMap.put(key, summary);
315                             }
316                         } // do nothing for NOT_EXECUTED scenes
317 
318                         if (sceneResult.isNull("mpc_metrics")) {
319                             continue;
320                         }
321                         // Update MPC level
322                         JSONArray metrics = sceneResult.getJSONArray("mpc_metrics");
323                         for (int i = 0; i < metrics.length(); i++) {
324                             String mpcResult = metrics.getString(i);
325                             if (!matchMpcResult(cameraId, mpcResult)) {
326                                 Log.e(TAG, "Error parsing MPC result string:" + mpcResult);
327                                 return;
328                             }
329                         }
330                     }
331                 } catch (org.json.JSONException e) {
332                     Log.e(TAG, "Error reading json result string:" + results , e);
333                     return;
334                 }
335 
336                 // Set summary if all scenes reported
337                 if (mSummaryMap.keySet().containsAll(mAllScenes)) {
338                     // Save test summary
339                     StringBuilder summary = new StringBuilder();
340                     for (String path : mSummaryMap.values()) {
341                         appendFileContentToSummary(summary, path);
342                     }
343                     if (summary.length() > MAX_SUMMARY_LEN) {
344                         Log.w(TAG, "ITS summary report too long: len: " + summary.length());
345                     }
346                     ItsTestActivity.this.getReportLog().setSummary(
347                             summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
348                 }
349 
350                 // Display current progress
351                 StringBuilder progress = new StringBuilder();
352                 for (ResultKey k : mAllScenes) {
353                     String status = RESULT_NOT_EXECUTED;
354                     if (mExecutedScenes.containsKey(k)) {
355                         status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL;
356                     }
357                     progress.append(String.format("Cam %s, %s: %s\n",
358                             k.cameraId, k.sceneId, status));
359                 }
360                 TextView progressView = (TextView) findViewById(R.id.its_progress);
361                 progressView.setMovementMethod(new ScrollingMovementMethod());
362                 progressView.setText(progress.toString());
363 
364 
365                 // Enable pass button if all scenes pass
366                 boolean allScenesPassed = true;
367                 for (ResultKey k : mAllScenes) {
368                     Boolean pass = mExecutedScenes.get(k);
369                     if (pass == null || pass == false) {
370                         allScenesPassed = false;
371                         break;
372                     }
373                 }
374                 if (allScenesPassed) {
375                     Log.i(TAG, "All scenes passed.");
376                     // Enable pass button
377                     ItsTestActivity.this.getPassButton().setEnabled(true);
378                     ItsTestActivity.this.setTestResultAndFinish(true);
379                 } else {
380                     ItsTestActivity.this.getPassButton().setEnabled(false);
381                 }
382             }
383         }
384 
appendFileContentToSummary(StringBuilder summary, String path)385         private void appendFileContentToSummary(StringBuilder summary, String path) {
386             BufferedReader reader = null;
387             try {
388                 reader = new BufferedReader(new FileReader(path));
389                 String line = null;
390                 do {
391                     line = reader.readLine();
392                     if (line != null) {
393                         summary.append(line);
394                     }
395                 } while (line != null);
396             } catch (FileNotFoundException e) {
397                 Log.e(TAG, "Cannot find ITS summary file at " + path);
398                 summary.append("Cannot find ITS summary file at " + path);
399             } catch (IOException e) {
400                 Log.e(TAG, "IO exception when trying to read " + path);
401                 summary.append("IO exception when trying to read " + path);
402             } finally {
403                 if (reader != null) {
404                     try {
405                         reader.close();
406                     } catch (IOException e) {
407                     }
408                 }
409             }
410         }
411 
matchMpcResult(String cameraId, String mpcResult)412         private boolean matchMpcResult(String cameraId, String mpcResult) {
413             Matcher launchMatcher = MPC12_CAMERA_LAUNCH_PATTERN.matcher(mpcResult);
414             boolean launchMatches = launchMatcher.matches();
415 
416             Matcher jpegMatcher = MPC12_JPEG_CAPTURE_PATTERN.matcher(mpcResult);
417             boolean jpegMatches = jpegMatcher.matches();
418 
419             if (!launchMatches && !jpegMatches) {
420                 return false;
421             }
422             if (!cameraId.equals(mPrimaryRearCameraId) &&
423                     !cameraId.equals(mPrimaryFrontCameraId)) {
424                 return false;
425             }
426 
427             if (launchMatches) {
428                 float latency = Float.parseFloat(launchMatcher.group(1));
429                 if (cameraId.equals(mPrimaryRearCameraId)) {
430                     mLaunchLatencyReq.setRearCameraLatency(latency);
431                 } else {
432                     mLaunchLatencyReq.setFrontCameraLatency(latency);
433                 }
434                 mExecutedMpcTests.add(new ResultKey(cameraId, MPC_LAUNCH_REQ_NUM));
435             } else {
436                 float latency = Float.parseFloat(jpegMatcher.group(1));
437                 if (cameraId.equals(mPrimaryRearCameraId)) {
438                     mJpegLatencyReq.setRearCameraLatency(latency);
439                 } else {
440                     mJpegLatencyReq.setFrontCameraLatency(latency);
441                 }
442                 mExecutedMpcTests.add(new ResultKey(cameraId, MPC_JPEG_CAPTURE_REQ_NUM));
443             }
444 
445             // Save MPC info once both front primary and rear primary data are collected.
446             if (mExecutedMpcTests.size() == 4) {
447                 mPce.submitAndVerify();
448             }
449             return true;
450         }
451     }
452 
453     private class FoldStateListener implements
454             DeviceStateManager.DeviceStateCallback {
455         private int[] mFoldedDeviceStates;
456         private boolean mFirstFoldCheck = false;
457 
FoldStateListener(Context context)458         FoldStateListener(Context context) {
459             Resources systemRes = Resources.getSystem();
460             int foldedStatesArrayIdentifier = systemRes.getIdentifier("config_foldedDeviceStates",
461                     "array", "android");
462             mFoldedDeviceStates = systemRes.getIntArray(foldedStatesArrayIdentifier);
463         }
464 
465         @Override
onStateChanged(int state)466         public final void onStateChanged(int state) {
467             boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
468             Log.i(TAG, "Is device folded? " + mIsDeviceFolded);
469             if (!mFirstFoldCheck || mIsDeviceFolded != folded) {
470                 mIsDeviceFolded = folded;
471                 mFirstFoldCheck = true;
472                 if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
473                     Log.i(TAG, "Setup is done for both the states.");
474                 } else {
475                     runOnUiThread(new Runnable() {
476                         @Override
477                         public void run() {
478                             Log.i(TAG, "set up from onStateChanged");
479                             getCameraIdsForFoldableDevice();
480                             setupItsTestsForFoldableDevice(mAdapter);
481                         }
482                     });
483                 }
484             } else {
485                 Log.i(TAG, "Last state is same as new state.");
486             }
487         }
488     }
489 
490     @Override
onCreate(Bundle savedInstanceState)491     protected void onCreate(Bundle savedInstanceState) {
492         // Hide the test if all camera devices are legacy
493         mCameraManager = this.getSystemService(CameraManager.class);
494         Context context = this.getApplicationContext();
495         if (mAllScenes == null) {
496             mAllScenes = new TreeSet<>(mComparator);
497         }
498         if (mExecutedMpcTests == null) {
499             mExecutedMpcTests = new TreeSet<>(mComparator);
500         }
501         mCameraThread = new HandlerThread("ItsTestActivityThread");
502         mCameraThread.start();
503         mCameraHandler = new Handler(mCameraThread.getLooper());
504         HandlerExecutor handlerExecutor = new HandlerExecutor(mCameraHandler);
505         // mIsFoldableDevice is set True for foldables to listen to callback
506         // in FoldStateListener
507         mIsFoldableDevice = isFoldableDevice();
508         Log.i(TAG, "Is device foldable? " + mIsFoldableDevice);
509         if (mIsFoldableDevice) {
510             FoldStateListener foldStateListener = new FoldStateListener(context);
511             mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
512             // onStateChanged will be called upon registration which helps determine
513             // if the foldable device has changed the folded/unfolded state or not.
514             mDeviceStateManager.registerCallback(handlerExecutor, foldStateListener);
515         }
516         if (!mIsFoldableDevice) {
517             try {
518                 ItsUtils.ItsCameraIdList cameraIdList =
519                         ItsUtils.getItsCompatibleCameraIds(mCameraManager);
520                 mToBeTestedCameraIds = cameraIdList.mCameraIdCombos;
521                 mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
522                 mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
523             } catch (ItsException e) {
524                 Toast.makeText(ItsTestActivity.this,
525                         "Received error from camera service while checking device capabilities: "
526                                 + e, Toast.LENGTH_SHORT).show();
527             }
528         }
529 
530         super.onCreate(savedInstanceState);
531 
532         if (!mIsFoldableDevice) {
533             if (mToBeTestedCameraIds.size() == 0) {
534                 showToast(R.string.all_exempted_devices);
535                 ItsTestActivity.this.getReportLog().setSummary(
536                         "PASS: all cameras on this device are exempted from ITS",
537                         1.0, ResultType.NEUTRAL, ResultUnit.NONE);
538                 setTestResultAndFinish(true);
539             }
540         }
541         // Default locale must be set to "en-us"
542         Locale locale = Locale.getDefault();
543         if (!Locale.US.equals(locale)) {
544             String toastMessage = "Unsupported default language " + locale + "! "
545                     + "Please switch the default language to English (United States) in "
546                     + "Settings > Language & input > Languages";
547             Toast.makeText(ItsTestActivity.this, toastMessage, Toast.LENGTH_LONG).show();
548             ItsTestActivity.this.getReportLog().setSummary(
549                     "FAIL: Default language is not set to " + Locale.US,
550                     1.0, ResultType.NEUTRAL, ResultUnit.NONE);
551             setTestResultAndFinish(false);
552         }
553         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
554     }
555 
getCameraIdsAvailableForTesting()556     private List<String> getCameraIdsAvailableForTesting() {
557         List<String> toBeTestedCameraIds = new ArrayList<String>();
558         List<String> availableCameraIdList = new ArrayList<String>();
559         try {
560             ItsUtils.ItsCameraIdList cameraIdList =
561                     ItsUtils.getItsCompatibleCameraIds(mCameraManager);
562             toBeTestedCameraIds = cameraIdList.mCameraIdCombos;
563             mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
564             mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
565             mUnavailablePhysicalCameras = getUnavailablePhysicalCameras();
566             Log.i(TAG, "unavailablePhysicalCameras:"
567                     + mUnavailablePhysicalCameras.toString());
568             for (String str : toBeTestedCameraIds) {
569                 if (str.contains(".")) {
570                     String[] strArr = str.split("\\.");
571                     if (mUnavailablePhysicalCameras.contains(new Pair<>(strArr[0], strArr[1]))) {
572                         toBeTestedCameraIds.remove(str);
573                     }
574                 }
575             }
576             Log.i(TAG, "AvailablePhysicalCameras to be tested:"
577                     + Arrays.asList(toBeTestedCameraIds.toString()));
578         } catch (ItsException e) {
579             Log.i(TAG, "Received error from camera service while checking device capabilities: "
580                     + e);
581         } catch (Exception e) {
582             Log.i(TAG, "Exception: " + e);
583         }
584 
585         return toBeTestedCameraIds;
586     }
587 
588     // Get camera ids available for testing for device in
589     // each state: folded and unfolded.
getCameraIdsForFoldableDevice()590     protected void getCameraIdsForFoldableDevice() {
591         boolean deviceFolded = mIsDeviceFolded;
592         try {
593             if (mIsDeviceFolded) {
594                 mToBeTestedCameraIdsFolded = getCameraIdsAvailableForTesting();
595             } else {
596                 mToBeTestedCameraIdsUnfolded = getCameraIdsAvailableForTesting();
597             }
598         } catch (Exception e) {
599             Log.i(TAG, "Exception: " + e);
600         }
601     }
602 
603     @Override
showManualTestDialog(final DialogTestListItem test, final DialogTestListItem.TestCallback callback)604     public void showManualTestDialog(final DialogTestListItem test,
605             final DialogTestListItem.TestCallback callback) {
606         //Nothing todo for ITS
607     }
608 
testTitle(String cam, String scene)609     protected String testTitle(String cam, String scene) {
610         return "Camera: " + cam + ", " + scene;
611     }
612 
613     // CtsVerifier has a "Folded" toggle that selectively surfaces some tests.
614     // To separate the tests in folded and unfolded states, CtsVerifier adds a [folded]
615     // suffix to the test id in its internal database depending on the state of the "Folded"
616     // toggle button. However, CameraITS has tests that it needs to persist across both folded
617     // and unfolded states.To get the test results to persist, we need CtsVerifier to store and
618     // look up the same test id regardless of the toggle button state.
619     // TODO(b/282804139): Update CTS tests to allow activities to write tests that persist
620     // across the states
testId(String cam, String scene)621     protected String testId(String cam, String scene) {
622         return "Camera_ITS_" + cam + "_" + scene + "[folded]";
623     }
624 
isFoldableDevice()625     protected boolean isFoldableDevice() {
626         Context context = this.getApplicationContext();
627         return CameraUtils.isDeviceFoldable(context);
628     }
629 
isDeviceFolded()630     protected boolean isDeviceFolded() {
631         return mIsDeviceFolded;
632     }
633 
getUnavailablePhysicalCameras()634     protected Set<Pair<String, String>> getUnavailablePhysicalCameras() throws ItsException {
635         final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
636                 new LinkedBlockingQueue<>();
637         mCameraThread = new HandlerThread("ItsCameraThread");
638         mCameraThread.start();
639         mCameraHandler = new Handler(mCameraThread.getLooper());
640         try {
641             CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
642                 @Override
643                 public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
644                     unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
645                 }
646             };
647             mCameraManager.registerAvailabilityCallback(ac, mCameraHandler);
648             Set<Pair<String, String>> unavailablePhysicalCameras =
649                     new HashSet<Pair<String, String>>();
650             Pair<String, String> candidatePhysicalIds =
651                     unavailablePhysicalCamEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
652                     java.util.concurrent.TimeUnit.MILLISECONDS);
653             while (candidatePhysicalIds != null) {
654                 unavailablePhysicalCameras.add(candidatePhysicalIds);
655                 candidatePhysicalIds =
656                         unavailablePhysicalCamEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
657                         java.util.concurrent.TimeUnit.MILLISECONDS);
658             }
659             mCameraManager.unregisterAvailabilityCallback(ac);
660             return unavailablePhysicalCameras;
661         } catch (Exception e) {
662             throw new ItsException("Exception: ", e);
663         }
664     }
665 
setupItsTests(ArrayTestListAdapter adapter)666     protected void setupItsTests(ArrayTestListAdapter adapter) {
667         for (String cam : mToBeTestedCameraIds) {
668             List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER)
669                     ? mHiddenPhysicalCameraSceneIds : mSceneIds;
670             for (String scene : scenes) {
671                 // Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
672                 // devices with sub-cameras.
673                 mAllScenes.add(new ResultKey(cam, scene));
674                 adapter.add(new DialogTestListItem(this,
675                         testTitle(cam, scene),
676                         testId(cam, scene)));
677             }
678             if (mExecutedMpcTests == null) {
679                 mExecutedMpcTests = new TreeSet<>(mComparator);
680             }
681             Log.d(TAG, "Total combinations to test on this device:" + mAllScenes.size());
682         }
683     }
684 
setupItsTestsForFoldableDevice(ArrayTestListAdapter adapter)685     protected void setupItsTestsForFoldableDevice(ArrayTestListAdapter adapter) {
686         List<String> toBeTestedCameraIds = new ArrayList<String>();
687         if (mIsDeviceFolded) {
688             toBeTestedCameraIds = mToBeTestedCameraIdsFolded;
689         } else {
690             toBeTestedCameraIds = mToBeTestedCameraIdsUnfolded;
691         }
692 
693         for (String cam : toBeTestedCameraIds) {
694             List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER)
695                     ? mHiddenPhysicalCameraSceneIds : mSceneIds;
696             for (String scene : scenes) {
697                 // Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
698                 // devices with sub-cameras.
699                 if (cam.contains(mPrimaryFrontCameraId) && mIsDeviceFolded) {
700                     scene = scene + "_folded";
701                 }
702                 // Rear camera scenes will be added only once.
703                 if (mAllScenes.contains(new ResultKey(cam, scene))) {
704                     continue;
705                 }
706                 // TODO(ruchamk): Remove extra logging after testing.
707                 Log.i(TAG, "Adding cam_id: " + cam + "scene: " + scene);
708                 mAllScenes.add(new ResultKey(cam, scene));
709                 adapter.add(new DialogTestListItem(this,
710                         testTitle(cam, scene),
711                         testId(cam, scene)));
712             }
713         }
714         Log.d(TAG, "Total combinations to test on this device:"
715                 + mAllScenes.size() + " folded? " + mIsDeviceFolded);
716         if (mIsDeviceFolded) {
717             mFoldedTestSetupDone = true;
718             Log.i(TAG, "mFoldedTestSetupDone");
719         } else {
720             mUnfoldedTestSetupDone = true;
721             Log.i(TAG, "mUnfoldedTestSetupDone");
722         }
723         if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
724             Log.d(TAG, "Total combinations to test on this foldable "
725                     + "device for both states:" + mAllScenes.size());
726         }
727         adapter.loadTestResults();
728     }
729 
730     @Override
setupTests(ArrayTestListAdapter adapter)731     protected void setupTests(ArrayTestListAdapter adapter) {
732         mAdapter = adapter;
733         if (mIsFoldableDevice) {
734             if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
735                 Log.i(TAG, "Set up is done");
736             }
737         } else {
738             setupItsTests(adapter);
739         }
740     }
741 
742     @Override
onResume()743     protected void onResume() {
744         super.onResume();
745         if (mCameraManager == null) {
746             showToast(R.string.no_camera_manager);
747         } else {
748             Log.d(TAG, "register ITS result receiver");
749             IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
750             registerReceiver(mResultsReceiver, filter, Context.RECEIVER_EXPORTED);
751             mReceiverRegistered = true;
752         }
753     }
754 
755     @Override
onDestroy()756     public void onDestroy() {
757         Log.d(TAG, "unregister ITS result receiver");
758         if (mReceiverRegistered) {
759             unregisterReceiver(mResultsReceiver);
760         }
761         super.onDestroy();
762     }
763 
764     @Override
onConfigurationChanged(Configuration newConfig)765     public void onConfigurationChanged(Configuration newConfig) {
766         super.onConfigurationChanged(newConfig);
767         setContentView(R.layout.its_main);
768         setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
769         setPassFailButtonClickListeners();
770         // Changing folded state can incorrectly enable pass button
771         ItsTestActivity.this.getPassButton().setEnabled(false);
772     }
773 }
774