Test for hidden api enforcement killswitch

Currently, simply verifies that we can access VMRuntime.THE_ONE, which
doesn't prove anything as there is no blacklisting enabled yet.

Refactor JNI parts of hidden API test into cts-signature-common to it can
be used in this test.

(cherry picked from commit d635f8936bb3baad00c4bf5658e3bc7f420a3421 in
master)

Bug: 64382372
Test: $ cts/tests/signature/runSignatureTests.sh
Change-Id: I8c566136dea2a6b5d41d81981d0f1ef88a0a1a5f
diff --git a/tests/signature/api-check/Android.mk b/tests/signature/api-check/Android.mk
index 4462393..94b59ba 100644
--- a/tests/signature/api-check/Android.mk
+++ b/tests/signature/api-check/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_MODULE := cts-api-signature-test
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     cts-signature-common \
diff --git a/tests/signature/api-check/hidden-api-killswitch/Android.mk b/tests/signature/api-check/hidden-api-killswitch/Android.mk
new file mode 100644
index 0000000..2e1ee05
--- /dev/null
+++ b/tests/signature/api-check/hidden-api-killswitch/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsHiddenApiKillswitchTestCases
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := libcts_dexchecker
+LOCAL_NDK_STL_VARIANT := c++_static
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := cts-api-signature-test
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/signature/api-check/hidden-api-killswitch/AndroidManifest.xml b/tests/signature/api-check/hidden-api-killswitch/AndroidManifest.xml
new file mode 100644
index 0000000..8699d18
--- /dev/null
+++ b/tests/signature/api-check/hidden-api-killswitch/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.signature.cts.api.killswitch">
+
+    <application/>
+
+    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.signature.cts.api.killswitch"
+                     android:label="Hidden API Killswitch Test"/>
+</manifest>
diff --git a/tests/signature/api-check/hidden-api-killswitch/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch/AndroidTest.xml
new file mode 100644
index 0000000..2d99a73
--- /dev/null
+++ b/tests/signature/api-check/hidden-api-killswitch/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<configuration description="Config for CTS Hidden API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- Enable the killswitch before running the test, then disable it afterwards. The test
+             is intended to verify the behaviour when the killswitch is enabled. -->
+        <option name="run-command" value="settings put global hidden_api_blacklist_exemptions \*" />
+        <option name="teardown-command" value="settings delete global hidden_api_blacklist_exemptions" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsHiddenApiKillswitchTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.killswitch" />
+        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.KillswitchTest" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/hidden-api/Android.mk b/tests/signature/api-check/hidden-api/Android.mk
index a5bc737..f039032 100644
--- a/tests/signature/api-check/hidden-api/Android.mk
+++ b/tests/signature/api-check/hidden-api/Android.mk
@@ -24,18 +24,8 @@
 $(eval $(call copy-one-file,$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST),$(LOCAL_BUILT_MODULE)))
 
 include $(CLEAR_VARS)
-LOCAL_MODULE := libcts_hiddenapi
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := hidden-api.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-include $(BUILD_SHARED_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_PACKAGE_NAME := CtsHiddenApiDiscoveryTestCases
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 LOCAL_SIGNATURE_API_FILES := blacklist.api
-LOCAL_JNI_SHARED_LIBRARIES := libcts_hiddenapi
+LOCAL_JNI_SHARED_LIBRARIES := libcts_dexchecker
 include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/hidden-api/hidden-api.cpp b/tests/signature/api-check/hidden-api/hidden-api.cpp
deleted file mode 100644
index fd2a94d..0000000
--- a/tests/signature/api-check/hidden-api/hidden-api.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#include "jni.h"
-
-class ScopedUtfChars {
- public:
-  ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
-    if (s == NULL) {
-      utf_chars_ = NULL;
-    } else {
-      utf_chars_ = env->GetStringUTFChars(s, NULL);
-    }
-  }
-
-  ~ScopedUtfChars() {
-    if (utf_chars_) {
-      env_->ReleaseStringUTFChars(string_, utf_chars_);
-    }
-  }
-
-  const char* c_str() const {
-    return utf_chars_;
-  }
-
- private:
-  JNIEnv* env_;
-  jstring string_;
-  const char* utf_chars_;
-};
-
-extern "C" JNIEXPORT void JNICALL
-Java_android_signature_cts_api_HiddenApiTest_getField_1JNI(
-    JNIEnv* env, jclass, jclass klass, jstring name, jstring type) {
-  ScopedUtfChars utf_name(env, name);
-  ScopedUtfChars utf_type(env, type);
-  // Attempt to access the given instance field. It will succeed if it exists,
-  // and throw NoSuchFieldError if not.
-  env->GetFieldID(klass, utf_name.c_str(), utf_type.c_str());
-}
-
-extern "C" JNIEXPORT void JNICALL
-Java_android_signature_cts_api_HiddenApiTest_getStaticField_1JNI(
-    JNIEnv* env, jclass, jclass klass, jstring name, jstring type) {
-  ScopedUtfChars utf_name(env, name);
-  ScopedUtfChars utf_type(env, type);
-  // Attempt to access the given static field. It will succeed if it exists,
-  // and throw NoSuchFieldError if not.
-  env->GetStaticFieldID(klass, utf_name.c_str(), utf_type.c_str());
-}
-
-extern "C" JNIEXPORT void JNICALL
-Java_android_signature_cts_api_HiddenApiTest_getMethod_1JNI(
-    JNIEnv* env, jclass, jclass klass, jstring name, jstring signature) {
-  ScopedUtfChars utf_name(env, name);
-  ScopedUtfChars utf_signature(env, signature);
-  // Attempt to access the given instance method. It will succeed if it exists,
-  // and throw NoSuchMethodError if not.
-  env->GetMethodID(klass, utf_name.c_str(), utf_signature.c_str());
-}
-
-extern "C" JNIEXPORT void JNICALL
-Java_android_signature_cts_api_HiddenApiTest_getStaticMethod_1JNI(
-    JNIEnv* env, jclass, jclass klass, jstring name, jstring signature) {
-  ScopedUtfChars utf_name(env, name);
-  ScopedUtfChars utf_signature(env, signature);
-  // Attempt to access the given static method. It will succeed if it exists,
-  // and throw NoSuchMethodError if not.
-  env->GetStaticMethodID(klass, utf_name.c_str(), utf_signature.c_str());
-}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
index e1da5ac..9673fd2 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
@@ -21,13 +21,17 @@
 import android.signature.cts.DexApiDocumentParser.DexField;
 import android.signature.cts.DexApiDocumentParser.DexMember;
 import android.signature.cts.DexApiDocumentParser.DexMethod;
+import android.signature.cts.DexMemberChecker;
 import android.signature.cts.FailureType;
+import android.signature.cts.ResultObserver;
+
 import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Stream;
 import java.text.ParseException;
 
@@ -52,136 +56,59 @@
      * Will check the entire API, and then report the complete list of failures
      */
     public void testSignature() {
-        System.loadLibrary("cts_hiddenapi");
+        DexMemberChecker.init();
         runWithTestResultObserver(resultObserver -> {
+            DexMemberChecker.Observer observer = new DexMemberChecker.Observer() {
+                @Override
+                public void classAccessible(boolean accessible, DexMember member) {
+                }
+
+                @Override
+                public void fieldAccessibleViaReflection(boolean accessible, DexField field) {
+                    if (accessible) {
+                        resultObserver.notifyFailure(
+                                FailureType.EXTRA_FIELD,
+                                field.toString(),
+                                "Hidden field accessible through reflection");
+                    }
+                }
+
+                @Override
+                public void fieldAccessibleViaJni(boolean accessible, DexField field) {
+                    if (accessible) {
+                        resultObserver.notifyFailure(
+                                FailureType.EXTRA_FIELD,
+                                field.toString(),
+                                "Hidden field accessible through JNI");
+                    }
+                }
+
+                @Override
+                public void methodAccessibleViaReflection(boolean accessible, DexMethod method) {
+                    if (accessible) {
+                        resultObserver.notifyFailure(
+                                FailureType.EXTRA_METHOD,
+                                method.toString(),
+                                "Hidden method accessible through reflection");
+                    }
+                }
+
+                @Override
+                public void methodAccessibleViaJni(boolean accessible, DexMethod method) {
+                    if (accessible) {
+                        resultObserver.notifyFailure(
+                                FailureType.EXTRA_METHOD,
+                                method.toString(),
+                                "Hidden method accessible through JNI");
+                    }
+                }
+            };
             parseDexApiFilesAsStream(hiddenApiFiles).forEach(dexMember -> {
-                checkSingleMember(dexMember, resultObserver);
+                DexMemberChecker.checkSingleMember(dexMember, observer);
             });
         });
     }
 
-    /**
-     * Check that a DexMember cannot be discovered with reflection or JNI, and
-     * record results in the result
-     */
-    private void checkSingleMember(DexMember dexMember, TestResultObserver resultObserver) {
-        Class<?> klass = findClass(dexMember);
-        if (klass == null) {
-            // Class not found. Therefore its members are not visible.
-            return;
-        }
-
-        if (dexMember instanceof DexField) {
-            if (hasMatchingField_Reflection(klass, (DexField) dexMember)) {
-                resultObserver.notifyFailure(
-                        FailureType.EXTRA_FIELD,
-                        dexMember.toString(),
-                        "Hidden field accessible through reflection");
-            }
-            if (hasMatchingField_JNI(klass, (DexField) dexMember)) {
-                resultObserver.notifyFailure(
-                        FailureType.EXTRA_FIELD,
-                        dexMember.toString(),
-                        "Hidden field accessible through JNI");
-            }
-        } else if (dexMember instanceof DexMethod) {
-            if (hasMatchingMethod_Reflection(klass, (DexMethod) dexMember)) {
-                resultObserver.notifyFailure(
-                        FailureType.EXTRA_METHOD,
-                        dexMember.toString(),
-                        "Hidden method accessible through reflection");
-            }
-            if (hasMatchingMethod_JNI(klass, (DexMethod) dexMember)) {
-                resultObserver.notifyFailure(
-                        FailureType.EXTRA_METHOD,
-                        dexMember.toString(),
-                        "Hidden method accessible through JNI");
-            }
-        } else {
-            throw new IllegalStateException("Unexpected type of dex member");
-        }
-    }
-
-    private boolean typesMatch(Class<?>[] classes, List<String> typeNames) {
-        if (classes.length != typeNames.size()) {
-            return false;
-        }
-        for (int i = 0; i < classes.length; ++i) {
-            if (!classes[i].getTypeName().equals(typeNames.get(i))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private Class<?> findClass(DexMember dexMember) {
-        Class<?> klass = null;
-        try {
-            return Class.forName(dexMember.getJavaClassName());
-        } catch (ClassNotFoundException ex) {
-            return null;
-        }
-    }
-
-    private static boolean hasMatchingField_Reflection(Class<?> klass, DexField dexField) {
-        try {
-            klass.getDeclaredField(dexField.getName());
-            return true;
-        } catch (NoSuchFieldException ex) {
-            return false;
-        }
-    }
-
-    private static boolean hasMatchingField_JNI(Class<?> klass, DexField dexField) {
-        try {
-            getField_JNI(klass, dexField.getName(), dexField.getDexType());
-            return true;
-        } catch (NoSuchFieldError ex) {
-        }
-        try {
-            getStaticField_JNI(klass, dexField.getName(), dexField.getDexType());
-            return true;
-        } catch (NoSuchFieldError ex) {
-        }
-        return false;
-    }
-
-    private boolean hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod) {
-        List<String> methodParams = dexMethod.getJavaParameterTypes();
-
-        if (dexMethod.isConstructor()) {
-            for (Constructor constructor : klass.getDeclaredConstructors()) {
-                if (typesMatch(constructor.getParameterTypes(), methodParams)) {
-                    return true;
-                }
-            }
-        } else {
-            for (Method method : klass.getDeclaredMethods()) {
-                if (method.getName().equals(dexMethod.getName())
-                        && typesMatch(method.getParameterTypes(), methodParams)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static boolean hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod) {
-        try {
-            getMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
-            return true;
-        } catch (NoSuchMethodError ex) {
-        }
-        if (!dexMethod.isConstructor()) {
-            try {
-                getStaticMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
-                return true;
-            } catch (NoSuchMethodError ex) {
-            }
-        }
-        return false;
-    }
-
     private static Stream<DexMember> parseDexApiFilesAsStream(String[] apiFiles) {
         DexApiDocumentParser dexApiDocumentParser = new DexApiDocumentParser();
         return Stream.of(apiFiles)
@@ -190,9 +117,4 @@
                 .flatMap(stream -> dexApiDocumentParser.parseAsStream(stream));
     }
 
-    private static native boolean getField_JNI(Class<?> klass, String name, String type);
-    private static native boolean getStaticField_JNI(Class<?> klass, String name, String type);
-    private static native boolean getMethod_JNI(Class<?> klass, String name, String signature);
-    private static native boolean getStaticMethod_JNI(Class<?> klass, String name,
-            String signature);
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/KillswitchTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/KillswitchTest.java
new file mode 100644
index 0000000..37ff746
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/KillswitchTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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 android.signature.cts.api;
+
+import android.provider.Settings;
+import android.signature.cts.DexApiDocumentParser;
+import android.signature.cts.DexMemberChecker;
+import android.signature.cts.FailureType;
+
+public class KillswitchTest extends AbstractApiTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        DexMemberChecker.init();
+        // precondition check: make sure global setting has been configured properly.
+        // This should be done via an adb command, configured in AndroidTest.xml.
+        assertEquals("Global setting " + Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
+                "*",
+                Settings.Global.getString(
+                        getInstrumentation().getContext().getContentResolver(),
+                        Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS));
+    }
+
+    private static final String ERROR_MESSAGE_APPENDIX =
+            " when global setting hidden_api_blacklist_exemptions is set to *";
+
+    public void testKillswitch() {
+        // for now, just verify that we can access something *not* on the light grey or white list
+        DexApiDocumentParser.DexMember member = new DexApiDocumentParser.DexField(
+                "Ldalvik/system/VMRuntime;", "THE_ONE", "Ldalvik/system/VMRuntime;");
+        runWithTestResultObserver(resultObserver ->
+                DexMemberChecker.checkSingleMember(member, new DexMemberChecker.Observer() {
+                    @Override
+                    public void classAccessible(boolean accessible,
+                            DexApiDocumentParser.DexMember member) {
+                        if (!accessible) {
+                            resultObserver.notifyFailure(
+                                    FailureType.MISSING_CLASS,
+                                    member.toString(),
+                                    "Class from boot classpath is not accessible"
+                                            + ERROR_MESSAGE_APPENDIX);
+                        }
+                    }
+
+                    @Override
+                    public void fieldAccessibleViaReflection(boolean accessible,
+                            DexApiDocumentParser.DexField field) {
+                        if (!accessible) {
+                            resultObserver.notifyFailure(
+                                    FailureType.MISSING_FIELD,
+                                    field.toString(),
+                                    "Field from boot classpath is not accessible via reflection"
+                                            + ERROR_MESSAGE_APPENDIX);
+                        }
+                    }
+
+                    @Override
+                    public void fieldAccessibleViaJni(boolean accessible,
+                            DexApiDocumentParser.DexField field) {
+                        if (!accessible) {
+                            resultObserver.notifyFailure(
+                                    FailureType.MISSING_FIELD,
+                                    field.toString(),
+                                    "Field from boot classpath is not accessible via JNI"
+                                            + ERROR_MESSAGE_APPENDIX);
+                        }
+                    }
+
+                    @Override
+                    public void methodAccessibleViaReflection(boolean accessible,
+                            DexApiDocumentParser.DexMethod method) {
+                        if (!accessible) {
+                            resultObserver.notifyFailure(
+                                    FailureType.MISSING_METHOD,
+                                    method.toString(),
+                                    "Method from boot classpath is not accessible via reflection"
+                                            + ERROR_MESSAGE_APPENDIX);
+                        }
+                    }
+
+                    @Override
+                    public void methodAccessibleViaJni(boolean accessible,
+                            DexApiDocumentParser.DexMethod method) {
+                        if (!accessible) {
+                            resultObserver.notifyFailure(
+                                    FailureType.MISSING_METHOD,
+                                    method.toString(),
+                                    "Method from boot classpath is not accessible via JNI"
+                                            + ERROR_MESSAGE_APPENDIX);
+                        }
+                    }
+
+                }));
+    }
+}