CameraITS: ItsTestActivity changes for foldables.

Bug: 261697137

Test: Installing CtsVerifier apk and verifying test
list setting up correctly.

Change-Id: I3f690d90687c79c68b355e26ed951c65a9ecf251
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 6d11ea5..5cc98fd 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -2970,7 +2970,7 @@
         <activity android:name=".camera.its.ItsTestActivity"
                   android:label="@string/camera_its_test"
                   android:launchMode="singleTop"
-                  android:configChanges="keyboardHidden|screenSize"
+                  android:configChanges="keyboardHidden|screenSize|screenLayout"
                   android:exported="true"
                   android:screenOrientation="landscape">
             <intent-filter>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 9f43e85..825461f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -21,44 +21,52 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.hardware.camera2.CameraManager;
+import android.hardware.cts.helpers.CameraUtils;
+import android.hardware.devicestate.DeviceStateManager;
 import android.mediapc.cts.common.PerformanceClassEvaluator;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
+import android.util.Pair;
 import android.view.WindowManager;
 import android.widget.TextView;
 import android.widget.Toast;
 
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.DialogTestListActivity;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.TestResult;
+import com.android.internal.util.ArrayUtils;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.junit.rules.TestName;
 
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * Test for Camera features that require that the camera be aimed at a specific test scene.
  * This test activity requires a USB connection to a computer, and a corresponding host-side run of
@@ -84,16 +92,32 @@
             Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
     private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
             Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
+    private static final int AVAILABILITY_TIMEOUT_MS = 10;
 
     private final ResultReceiver mResultsReceiver = new ResultReceiver();
     private boolean mReceiverRegistered = false;
 
     public final TestName mTestName = new TestName();
+    private  boolean mIsFoldableDevice = false;
+    private  boolean mIsDeviceFolded = false;
+    private  boolean mFoldedTestSetupDone = false;
+    private  boolean mUnfoldedTestSetupDone = false;
+    private  Set<Pair<String, String>> mUnavailablePhysicalCameras =
+            new HashSet<Pair<String, String>>();
+    private CameraManager mCameraManager = null;
+    private DeviceStateManager mDeviceStateManager = null;
+    private HandlerThread mCameraThread = null;
+    private Handler mCameraHandler = null;
 
     // Initialized in onCreate
     List<String> mToBeTestedCameraIds = null;
-    String mPrimaryRearCameraId = null;
-    String mPrimaryFrontCameraId = null;
+    private  String mPrimaryRearCameraId = null;
+    private  String mPrimaryFrontCameraId = null;
+    private  List<String> mToBeTestedCameraIdsUnfolded = null;
+    private  List<String> mToBeTestedCameraIdsFolded = null;
+    private  String mPrimaryRearCameraIdUnfolded = null;
+    private  String mPrimaryFrontCameraIdUnfolded = null;
+    private ArrayTestListAdapter mAdapter;
 
     // Scenes
     private static final ArrayList<String> mSceneIds = new ArrayList<String> () {{
@@ -138,6 +162,18 @@
     PerformanceClassEvaluator.CameraLatencyRequirement mLaunchLatencyReq =
             mPce.addR7_5__H_1_6();
 
+    private static class HandlerExecutor implements Executor {
+        private final Handler mHandler;
+
+        HandlerExecutor(Handler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void execute(Runnable runCmd) {
+            mHandler.post(runCmd);
+        }
+    }
 
     final class ResultKey {
         public final String cameraId;
@@ -204,9 +240,23 @@
                     return;
                 }
 
-                if (!mToBeTestedCameraIds.contains(cameraId)) {
-                    Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
-                    return;
+                if (mIsFoldableDevice) {
+                    if (!mIsDeviceFolded) {
+                        if (!mToBeTestedCameraIdsUnfolded.contains(cameraId)) {
+                            Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
+                            return;
+                        }
+                    } else {
+                        if (!mToBeTestedCameraIdsFolded.contains(cameraId)) {
+                            Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
+                            return;
+                        }
+                    }
+                } else {
+                    if (!mToBeTestedCameraIds.contains(cameraId)) {
+                        Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
+                        return;
+                    }
                 }
 
                 try {
@@ -398,36 +448,100 @@
         }
     }
 
+    private class FoldStateListener implements
+            DeviceStateManager.DeviceStateCallback {
+        private int[] mFoldedDeviceStates;
+        private boolean mFirstFoldCheck = false;
+
+        FoldStateListener(Context context) {
+            Resources systemRes = Resources.getSystem();
+            int foldedStatesArrayIdentifier = systemRes.getIdentifier("config_foldedDeviceStates",
+                    "array", "android");
+            mFoldedDeviceStates = systemRes.getIntArray(foldedStatesArrayIdentifier);
+        }
+
+        @Override
+        public final void onStateChanged(int state) {
+            boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+            Log.i(TAG, "Is device folded? " + mIsDeviceFolded);
+            if (!mFirstFoldCheck || mIsDeviceFolded != folded) {
+                mIsDeviceFolded = folded;
+                mFirstFoldCheck = true;
+                if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
+                    Log.i(TAG, "Setup is done for both the states.");
+                } else {
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Log.i(TAG, "set up from onStateChanged");
+                            getCameraIdsForFoldableDevice();
+                            setupItsTestsForFoldableDevice(mAdapter);
+                        }
+                    });
+                }
+            } else {
+                Log.i(TAG, "Last state is same as new state.");
+            }
+        }
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         // Hide the test if all camera devices are legacy
-        CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
-        try {
-            ItsUtils.ItsCameraIdList cameraIdList =
-                    ItsUtils.getItsCompatibleCameraIds(manager);
-            mToBeTestedCameraIds = cameraIdList.mCameraIdCombos;
-            mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
-            mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
-        } catch (ItsException e) {
-            Toast.makeText(ItsTestActivity.this,
-                    "Received error from camera service while checking device capabilities: "
-                            + e, Toast.LENGTH_SHORT).show();
+        mCameraManager = this.getSystemService(CameraManager.class);
+        Context context = this.getApplicationContext();
+        if (mAllScenes == null) {
+            mAllScenes = new TreeSet<>(mComparator);
+        }
+        if (mExecutedMpcTests == null) {
+            mExecutedMpcTests = new TreeSet<>(mComparator);
+        }
+        mCameraThread = new HandlerThread("ItsTestActivityThread");
+        mCameraThread.start();
+        mCameraHandler = new Handler(mCameraThread.getLooper());
+        HandlerExecutor handlerExecutor = new HandlerExecutor(mCameraHandler);
+        // mIsFoldableDevice is set True for foldables to listen to callback
+        // in FoldStateListener
+        mIsFoldableDevice = isFoldableDevice();
+        Log.i(TAG, "Is device foldable? " + mIsFoldableDevice);
+        if (mIsFoldableDevice) {
+            FoldStateListener foldStateListener = new FoldStateListener(context);
+            mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+            // onStateChanged will be called upon registration which helps determine
+            // if the foldable device has changed the folded/unfolded state or not.
+            mDeviceStateManager.registerCallback(handlerExecutor, foldStateListener);
+        }
+        if (!mIsFoldableDevice) {
+            try {
+                ItsUtils.ItsCameraIdList cameraIdList =
+                        ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+                mToBeTestedCameraIds = cameraIdList.mCameraIdCombos;
+                mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
+                mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
+            } catch (ItsException e) {
+                Toast.makeText(ItsTestActivity.this,
+                        "Received error from camera service while checking device capabilities: "
+                                + e, Toast.LENGTH_SHORT).show();
+            }
         }
 
         super.onCreate(savedInstanceState);
-        if (mToBeTestedCameraIds.size() == 0) {
-            showToast(R.string.all_exempted_devices);
-            ItsTestActivity.this.getReportLog().setSummary(
-                    "PASS: all cameras on this device are exempted from ITS"
-                    , 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
-            setTestResultAndFinish(true);
+
+        if (!mIsFoldableDevice) {
+            if (mToBeTestedCameraIds.size() == 0) {
+                showToast(R.string.all_exempted_devices);
+                ItsTestActivity.this.getReportLog().setSummary(
+                        "PASS: all cameras on this device are exempted from ITS",
+                        1.0, ResultType.NEUTRAL, ResultUnit.NONE);
+                setTestResultAndFinish(true);
+            }
         }
         // Default locale must be set to "en-us"
         Locale locale = Locale.getDefault();
         if (!Locale.US.equals(locale)) {
-            String toastMessage = "Unsupported default language " + locale + "! " +
-                    "Please switch the default language to English (United States) in " +
-                    "Settings > Language & input > Languages";
+            String toastMessage = "Unsupported default language " + locale + "! "
+                    + "Please switch the default language to English (United States) in "
+                    + "Settings > Language & input > Languages";
             Toast.makeText(ItsTestActivity.this, toastMessage, Toast.LENGTH_LONG).show();
             ItsTestActivity.this.getReportLog().setSummary(
                     "FAIL: Default language is not set to " + Locale.US,
@@ -437,6 +551,53 @@
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
 
+    private List<String> getCameraIdsAvailableForTesting() {
+        List<String> toBeTestedCameraIds = new ArrayList<String>();
+        List<String> availableCameraIdList = new ArrayList<String>();
+        try {
+            ItsUtils.ItsCameraIdList cameraIdList =
+                    ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+            toBeTestedCameraIds = cameraIdList.mCameraIdCombos;
+            mPrimaryRearCameraId = cameraIdList.mPrimaryRearCameraId;
+            mPrimaryFrontCameraId = cameraIdList.mPrimaryFrontCameraId;
+            mUnavailablePhysicalCameras = getUnavailablePhysicalCameras();
+            Log.i(TAG, "unavailablePhysicalCameras:"
+                    + mUnavailablePhysicalCameras.toString());
+            for (String str : toBeTestedCameraIds) {
+                if (str.contains(".")) {
+                    String[] strArr = str.split("\\.");
+                    if (mUnavailablePhysicalCameras.contains(new Pair<>(strArr[0], strArr[1]))) {
+                        toBeTestedCameraIds.remove(str);
+                    }
+                }
+            }
+            Log.i(TAG, "AvailablePhysicalCameras to be tested:"
+                    + Arrays.asList(toBeTestedCameraIds.toString()));
+        } catch (ItsException e) {
+            Log.i(TAG, "Received error from camera service while checking device capabilities: "
+                    + e);
+        } catch (Exception e) {
+            Log.i(TAG, "Exception: " + e);
+        }
+
+        return toBeTestedCameraIds;
+    }
+
+    // Get camera ids available for testing for device in
+    // each state: folded and unfolded.
+    protected void getCameraIdsForFoldableDevice() {
+        boolean deviceFolded = mIsDeviceFolded;
+        try {
+            if (mIsDeviceFolded) {
+                mToBeTestedCameraIdsFolded = getCameraIdsAvailableForTesting();
+            } else {
+                mToBeTestedCameraIdsUnfolded = getCameraIdsAvailableForTesting();
+            }
+        } catch (Exception e) {
+            Log.i(TAG, "Exception: " + e);
+        }
+    }
+
     @Override
     public void showManualTestDialog(final DialogTestListItem test,
             final DialogTestListItem.TestCallback callback) {
@@ -451,38 +612,127 @@
         return "Camera_ITS_" + cam + "_" + scene;
     }
 
+    protected boolean isFoldableDevice() {
+        Context context = this.getApplicationContext();
+        return CameraUtils.isDeviceFoldable(context);
+    }
+
+    protected boolean isDeviceFolded() {
+        return mIsDeviceFolded;
+    }
+
+    protected Set<Pair<String, String>> getUnavailablePhysicalCameras() throws ItsException {
+        final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
+                new LinkedBlockingQueue<>();
+        mCameraThread = new HandlerThread("ItsCameraThread");
+        mCameraThread.start();
+        mCameraHandler = new Handler(mCameraThread.getLooper());
+        try {
+            CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
+                @Override
+                public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
+                    unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
+                }
+            };
+            mCameraManager.registerAvailabilityCallback(ac, mCameraHandler);
+            Set<Pair<String, String>> unavailablePhysicalCameras =
+                    new HashSet<Pair<String, String>>();
+            Pair<String, String> candidatePhysicalIds =
+                    unavailablePhysicalCamEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                    java.util.concurrent.TimeUnit.MILLISECONDS);
+            while (candidatePhysicalIds != null) {
+                unavailablePhysicalCameras.add(candidatePhysicalIds);
+                candidatePhysicalIds =
+                        unavailablePhysicalCamEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                        java.util.concurrent.TimeUnit.MILLISECONDS);
+            }
+            mCameraManager.unregisterAvailabilityCallback(ac);
+            return unavailablePhysicalCameras;
+        } catch (Exception e) {
+            throw new ItsException("Exception: ", e);
+        }
+    }
+
     protected void setupItsTests(ArrayTestListAdapter adapter) {
         for (String cam : mToBeTestedCameraIds) {
-            List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER) ?
-                    mHiddenPhysicalCameraSceneIds : mSceneIds;
+            List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER)
+                    ? mHiddenPhysicalCameraSceneIds : mSceneIds;
             for (String scene : scenes) {
                 // Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
                 // devices with sub-cameras.
-                if(mAllScenes == null){
-                    mAllScenes = new TreeSet<>(mComparator);
-                }
                 mAllScenes.add(new ResultKey(cam, scene));
                 adapter.add(new DialogTestListItem(this,
-                testTitle(cam, scene),
-                testId(cam, scene)));
+                        testTitle(cam, scene),
+                        testId(cam, scene)));
             }
             if (mExecutedMpcTests == null) {
                 mExecutedMpcTests = new TreeSet<>(mComparator);
             }
-            Log.d(TAG,"Total combinations to test on this device:" + mAllScenes.size());
+            Log.d(TAG, "Total combinations to test on this device:" + mAllScenes.size());
         }
     }
 
+    protected void setupItsTestsForFoldableDevice(ArrayTestListAdapter adapter) {
+        List<String> toBeTestedCameraIds = new ArrayList<String>();
+        if (mIsDeviceFolded) {
+            toBeTestedCameraIds = mToBeTestedCameraIdsFolded;
+        } else {
+            toBeTestedCameraIds = mToBeTestedCameraIdsUnfolded;
+        }
+
+        for (String cam : toBeTestedCameraIds) {
+            List<String> scenes = cam.contains(ItsUtils.CAMERA_ID_TOKENIZER)
+                    ? mHiddenPhysicalCameraSceneIds : mSceneIds;
+            for (String scene : scenes) {
+                // Add camera and scene combinations in mAllScenes to avoid adding n/a scenes for
+                // devices with sub-cameras.
+                if (cam.contains(mPrimaryFrontCameraId) && mIsDeviceFolded) {
+                    scene = scene + "_folded";
+                }
+                // Rear camera scenes will be added only once.
+                if (mAllScenes.contains(new ResultKey(cam, scene))) {
+                    continue;
+                }
+                // TODO(ruchamk): Remove extra logging after testing.
+                Log.i(TAG, "Adding cam_id: " + cam + "scene: " + scene);
+                mAllScenes.add(new ResultKey(cam, scene));
+                adapter.add(new DialogTestListItem(this,
+                        testTitle(cam, scene),
+                        testId(cam, scene)));
+            }
+        }
+        Log.d(TAG, "Total combinations to test on this device:"
+                + mAllScenes.size() + " folded? " + mIsDeviceFolded);
+        if (mIsDeviceFolded) {
+            mFoldedTestSetupDone = true;
+            Log.i(TAG, "mFoldedTestSetupDone");
+        } else {
+            mUnfoldedTestSetupDone = true;
+            Log.i(TAG, "mUnfoldedTestSetupDone");
+        }
+        if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
+            Log.d(TAG, "Total combinations to test on this foldable "
+                    + "device for both states:" + mAllScenes.size());
+        }
+        adapter.loadTestResults();
+    }
+
     @Override
     protected void setupTests(ArrayTestListAdapter adapter) {
-        setupItsTests(adapter);
+        mAdapter = adapter;
+        if (mIsFoldableDevice) {
+            if (mFoldedTestSetupDone && mUnfoldedTestSetupDone) {
+                Log.i(TAG, "Set up is done");
+            }
+        } else {
+            setupItsTests(adapter);
+        }
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-        CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
-        if (manager == null) {
+        if (mCameraManager == null) {
             showToast(R.string.no_camera_manager);
         } else {
             Log.d(TAG, "register ITS result receiver");