Introduce a Libcore-specific version of `CtsTestRunListener`.

Some Libcore CTS tests depend on logic in
`com.android.cts.runner.CtsTestRunListener`. As this listener is being
deprecated (see b/267766325), introduce a specific version just for
Libcore CTS tests (`com.android.cts.runner.CtsLibcoreTestRunListener`).

Test: atest CtsLibcoreJsr166TestCases
Test: atest CtsLibcoreOjTestCases
Test: atest CtsLibcoreOkHttpTestCases
Test: atest CtsLibcoreTestCases
Test: atest CtsLibcoreWycheproofBCTestCases
Test: atest CtsLibcoreWycheproofConscryptTestCases
Test: atest MtsLibcoreOkHttpTestCases
Bug: 271428393
Bug: 267766325
Change-Id: I082725d3263eeae4a142e90440a162c3bc6c9d76
diff --git a/tests/core/runner-axt/src/com/android/cts/runner/CtsLibcoreTestRunListener.java b/tests/core/runner-axt/src/com/android/cts/runner/CtsLibcoreTestRunListener.java
new file mode 100644
index 0000000..dfd78a7
--- /dev/null
+++ b/tests/core/runner-axt/src/com/android/cts/runner/CtsLibcoreTestRunListener.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.runner;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.internal.runner.listener.InstrumentationRunListener;
+
+import junit.framework.TestCase;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunListener;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Class;
+import java.lang.ReflectiveOperationException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ResponseCache;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * A {@link RunListener} for Libcore CTS tests. Sets the system properties
+ * necessary for many core tests to run. This is needed because there are some
+ * core tests that need writing access to the file system.
+ * Finally, we add a means to free memory allocated by a TestCase after its
+ * execution.
+ */
+public class CtsLibcoreTestRunListener extends InstrumentationRunListener {
+
+    private static final String TAG = "CtsLibcoreTestRunListener";
+
+    private TestEnvironment mEnvironment;
+    private Class<?> lastClass;
+
+    @Override
+    public void testRunStarted(Description description) throws Exception {
+        mEnvironment = new TestEnvironment(getInstrumentation().getTargetContext());
+
+        // We might want to move this to /sdcard, if is is mounted/writable.
+        File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
+        System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+
+        // attempt to disable keyguard, if current test has permission to do so
+        // TODO: move this to a better place, such as InstrumentationTestRunner
+        // ?
+        if (getInstrumentation().getContext().checkCallingOrSelfPermission(
+                android.Manifest.permission.DISABLE_KEYGUARD)
+                == PackageManager.PERMISSION_GRANTED) {
+            Log.i(TAG, "Disabling keyguard");
+            KeyguardManager keyguardManager =
+                    (KeyguardManager) getInstrumentation().getContext().getSystemService(
+                            Context.KEYGUARD_SERVICE);
+            keyguardManager.newKeyguardLock("cts").disableKeyguard();
+        } else {
+            Log.i(TAG, "Test lacks permission to disable keyguard. " +
+                    "UI based tests may fail if keyguard is up");
+        }
+    }
+
+    @Override
+    public void testStarted(Description description) throws Exception {
+        if (description.getTestClass() != lastClass) {
+            lastClass = description.getTestClass();
+            printMemory(description.getTestClass());
+        }
+
+        mEnvironment.reset();
+    }
+
+    @Override
+    public void testFinished(Description description) {
+        // no way to implement this in JUnit4...
+        // offending test cases that need this logic should probably be cleaned
+        // up individually
+        // if (test instanceof TestCase) {
+        // cleanup((TestCase) test);
+        // }
+    }
+
+    /**
+     * Dumps some memory info.
+     */
+    private void printMemory(Class<?> testClass) {
+        Runtime runtime = Runtime.getRuntime();
+
+        long total = runtime.totalMemory();
+        long free = runtime.freeMemory();
+        long used = total - free;
+
+        Log.d(TAG, "Total memory  : " + total);
+        Log.d(TAG, "Used memory   : " + used);
+        Log.d(TAG, "Free memory   : " + free);
+
+        String tempdir = System.getProperty("java.io.tmpdir", "");
+        // TODO: Remove these extra Logs added to debug a specific timeout problem.
+        Log.d(TAG, "java.io.tmpdir is:" + tempdir);
+
+        if (!TextUtils.isEmpty(tempdir)) {
+            String[] commands = {"df", tempdir};
+            BufferedReader in = null;
+            try {
+                Log.d(TAG, "About to .exec df");
+                Process proc = runtime.exec(commands);
+                Log.d(TAG, ".exec returned");
+                in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
+                Log.d(TAG, "Stream reader created");
+                String line;
+                while ((line = in.readLine()) != null) {
+                    Log.d(TAG, line);
+                }
+            } catch (IOException e) {
+                Log.d(TAG, "Exception: " + e.toString());
+                // Well, we tried
+            } finally {
+                Log.d(TAG, "In finally");
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        // Meh
+                    }
+                }
+            }
+        }
+
+        Log.d(TAG, "Now executing : " + testClass.getName());
+    }
+
+    /**
+     * Nulls all non-static reference fields in the given test class. This
+     * method helps us with those test classes that don't have an explicit
+     * tearDown() method. Normally the garbage collector should take care of
+     * everything, but since JUnit keeps references to all test cases, a little
+     * help might be a good idea.
+     */
+    private void cleanup(TestCase test) {
+        Class<?> clazz = test.getClass();
+
+        while (clazz != TestCase.class) {
+            Field[] fields = clazz.getDeclaredFields();
+            for (int i = 0; i < fields.length; i++) {
+                Field f = fields[i];
+                if (!f.getType().isPrimitive() &&
+                        !Modifier.isStatic(f.getModifiers())) {
+                    try {
+                        f.setAccessible(true);
+                        f.set(test, null);
+                    } catch (Exception ignored) {
+                        // Nothing we can do about it.
+                    }
+                }
+            }
+
+            clazz = clazz.getSuperclass();
+        }
+    }
+
+    private interface TestEnvironmentResetter {
+        Boolean getDateFormatIs24Hour();
+        void setDateFormatIs24Hour(Boolean value);
+        Properties createDefaultProperties();
+    }
+
+    private static class AndroidTestEnvironmentResetter implements TestEnvironmentResetter {
+        private final Field mDateFormatIs24HourField;
+
+        AndroidTestEnvironmentResetter() {
+            try {
+                Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
+                mDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Missing DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public Boolean getDateFormatIs24Hour() {
+            try {
+                return (Boolean) mDateFormatIs24HourField.get(null);
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public void setDateFormatIs24Hour(Boolean value) {
+            try {
+                mDateFormatIs24HourField.set(null, value);
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public Properties createDefaultProperties() {
+            return new Properties();
+        }
+    }
+
+    private static class StubTestEnvironmentResetter implements TestEnvironmentResetter {
+        @Override
+        public Boolean getDateFormatIs24Hour() {
+            return false;
+        }
+
+        @Override
+        public void setDateFormatIs24Hour(Boolean value) {
+        }
+
+        @Override
+        public Properties createDefaultProperties() {
+            return System.getProperties();
+        }
+    }
+
+    // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
+    static class TestEnvironment {
+        private final static TestEnvironmentResetter sTestEnvironmentResetter;
+        static {
+            if (System.getProperty("java.vendor").toLowerCase().contains("android")) {
+                sTestEnvironmentResetter = new AndroidTestEnvironmentResetter();
+            } else {
+                sTestEnvironmentResetter = new StubTestEnvironmentResetter();
+            }
+        }
+
+        private final Locale mDefaultLocale;
+        private final TimeZone mDefaultTimeZone;
+        private final HostnameVerifier mHostnameVerifier;
+        private final SSLSocketFactory mSslSocketFactory;
+        private final Properties mProperties;
+        private final Boolean mDefaultIs24Hour;
+
+        TestEnvironment(Context context) {
+            mDefaultLocale = Locale.getDefault();
+            mDefaultTimeZone = TimeZone.getDefault();
+            mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+            mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+
+            mProperties = sTestEnvironmentResetter.createDefaultProperties();
+            mProperties.setProperty("user.home", "");
+            mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
+            // The CDD mandates that devices that support WiFi are the only ones that will have
+            // multicast.
+            PackageManager pm = context.getPackageManager();
+            mProperties.setProperty("android.cts.device.multicast",
+                    Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
+            mDefaultIs24Hour = sTestEnvironmentResetter.getDateFormatIs24Hour();
+
+            // There are tests in libcore that should be disabled for low ram devices. They can't
+            // access ActivityManager to call isLowRamDevice, but can read system properties.
+            ActivityManager activityManager =
+                    (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+            mProperties.setProperty("android.cts.device.lowram",
+                    Boolean.toString(activityManager.isLowRamDevice()));
+        }
+
+        void reset() {
+            System.setProperties(null);
+            System.setProperties(mProperties);
+            Locale.setDefault(mDefaultLocale);
+            TimeZone.setDefault(mDefaultTimeZone);
+            Authenticator.setDefault(null);
+            CookieHandler.setDefault(null);
+            ResponseCache.setDefault(null);
+            HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
+            HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
+            sTestEnvironmentResetter.setDateFormatIs24Hour(mDefaultIs24Hour);
+        }
+    }
+}
diff --git a/tests/libcore/jsr166/AndroidManifest.xml b/tests/libcore/jsr166/AndroidManifest.xml
index fb81cdb..05cfb63 100644
--- a/tests/libcore/jsr166/AndroidManifest.xml
+++ b/tests/libcore/jsr166/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.jsr166"
                      android:label="CTS Libcore JSR166 test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsLibcoreTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/luni/AndroidManifest.xml b/tests/libcore/luni/AndroidManifest.xml
index 558a7db..3c5eb86 100644
--- a/tests/libcore/luni/AndroidManifest.xml
+++ b/tests/libcore/luni/AndroidManifest.xml
@@ -24,6 +24,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts"
                      android:label="CTS Libcore test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsLibcoreTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/ojluni/AndroidManifest.xml b/tests/libcore/ojluni/AndroidManifest.xml
index bf03e8f..affeb8b 100644
--- a/tests/libcore/ojluni/AndroidManifest.xml
+++ b/tests/libcore/ojluni/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.runner"
                      android:label="CTS Libcore OJ test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsLibcoreTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/okhttp/AndroidManifest.xml b/tests/libcore/okhttp/AndroidManifest.xml
index 3848e7e..da0e6f6 100644
--- a/tests/libcore/okhttp/AndroidManifest.xml
+++ b/tests/libcore/okhttp/AndroidManifest.xml
@@ -25,6 +25,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.okhttp"
                      android:label="CTS Libcore OkHttp test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsLibcoreTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/wycheproof-bc/AndroidManifest.xml b/tests/libcore/wycheproof-bc/AndroidManifest.xml
index a867fae..92370e7 100644
--- a/tests/libcore/wycheproof-bc/AndroidManifest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidManifest.xml
@@ -24,6 +24,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.wycheproof.bouncycastle"
                      android:label="CTS Libcore Wycheproof Bouncy Castle test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsLibcoreTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/wycheproof/AndroidManifest.xml b/tests/libcore/wycheproof/AndroidManifest.xml
index 371cd08..503ba01 100644
--- a/tests/libcore/wycheproof/AndroidManifest.xml
+++ b/tests/libcore/wycheproof/AndroidManifest.xml
@@ -24,6 +24,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.wycheproof.conscrypt"
                      android:label="CTS Libcore Wycheproof Conscrypt test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsLibcoreTestRunListener" />
     </instrumentation>
 
 </manifest>