Add a new test for uses-native-library tag
CtsUsesNativeLibraryTest is added to test the new uses-native-library
tag. The tag is introduced for apps targeting Android S or higher,
and is used to declare the dependencies to the native shared libraries
that the platform exports (in addition to the NDK libraries).
The configuratio of the test is somewhat unique. The host-side test
reads the list of public native shared libraries from DUT and builds
test apps with different values of uses-native-library tags.
Then the test app is installed to the device as a self-instrumentation
test. The device-side test makes sure that libraries are loadable or
non-loadable as expected.
Bug: 142191088
Test: atest CtsUsesNativeLibraryTest
Change-Id: I445d2075c2674911da9a9e94e4e0a9dd853a7fec
diff --git a/hostsidetests/library/Android.bp b/hostsidetests/library/Android.bp
new file mode 100644
index 0000000..ccf441e
--- /dev/null
+++ b/hostsidetests/library/Android.bp
@@ -0,0 +1,106 @@
+// Copyright (C) 2020 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.
+
+java_test_host {
+ name: "CtsUsesNativeLibraryTest",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ ],
+ java_resource_dirs: ["res"],
+ data: [":CtsUesNativeLibraryBuildPackage"],
+}
+
+// Note that this app is built as a java library. The actual app is built
+// by the test (CtsUsesNativeLibraryTest) while the test is running.
+// This java library is appended to the built apk by the test.
+java_library {
+ name: "CtsUsesNativeLibraryTestApp",
+ srcs: ["src_target/**/*.java"],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "android-support-test"
+ ],
+ sdk_version: "current",
+ compile_dex: true,
+ installable: false,
+ visibility: ["//visibility:private"],
+}
+
+// These are collection of tools and libraries that are required to build
+// an apk by the test. This zip file is extracted by the test and files
+// in the zip are executed from there.
+//
+// There are two tricks used here: 1) host tools such as aapt2 are listed
+// in the `tools` property although they technically are inputs of the zip,
+// not the tools for creating the zip. However, since the java test is not
+// specific to arch, it can't (transitively) depend on arch-specific (x86)
+// host tools. To work-around the problem, they are listed in the `tools`
+// property, and then used as inputs in the `cmd`.
+//
+// 2) signapk and libconscrypt_openjdk_jni are listed in the `host_required`
+// property instead of `tools` or `srcs`. This is because those modules are
+// neither specific to arch (thus can't be in tools), nor provide source (thus
+// can't be in srcs). To access them, their location in the soong intermediate
+// directory is manually searched in the cmd, while dependencies to them are
+// created using the `required` property.
+genrule {
+ name: "CtsUesNativeLibraryBuildPackage",
+ // srcs, tools, required are all "essentially" inputs of the zip
+ // (except for soong_zip which is actually the tool)
+ srcs: [
+ ":CtsUsesNativeLibraryTestApp",
+ ":sdk_public_30_android",
+ "testkey.pk8",
+ "testkey.x509.pem",
+ ],
+ tools: [
+ "aapt2",
+ "soong_zip",
+ "merge_zips",
+ // To make signapk.jar be generated under HOST_SOONG_OUT before this rule runes
+ "signapk",
+ ],
+ host_required: [
+ "signapk",
+ "libconscrypt_openjdk_jni",
+ ],
+ out: ["CtsUesNativeLibraryBuildPackage.zip"],
+ // Copied from system/apex/apexer/Android.bp
+ cmd: "HOST_OUT_BIN=$$(dirname $(location soong_zip)) && " +
+ "HOST_SOONG_OUT=$$(dirname $$(dirname $$HOST_OUT_BIN)) && " +
+ "SIGNAPK_JAR=$$(find $$HOST_SOONG_OUT -name \"signapk*\") && " +
+ "LIBCONSCRYPT_OPENJDK_JNI=$$(find $$HOST_SOONG_OUT -name \"libconscrypt_openjdk_jni.*\") && " +
+ "rm -rf $(genDir)/content && " +
+ "mkdir -p $(genDir)/content && " +
+ "cp $(location aapt2) $(genDir)/content && " +
+ "cp $(location merge_zips) $(genDir)/content && " +
+ "cp $(location :sdk_public_30_android) $(genDir)/content && " +
+ "cp $(location :CtsUsesNativeLibraryTestApp) $(genDir)/content && " +
+ "cp $(location testkey.pk8) $(genDir)/content && " +
+ "cp $(location testkey.x509.pem) $(genDir)/content && " +
+ "cp $$SIGNAPK_JAR $(genDir)/content && " +
+ "cp $$LIBCONSCRYPT_OPENJDK_JNI $(genDir)/content && " +
+ "$(location soong_zip) -C $(genDir)/content -D $(genDir)/content -o $(out) && " +
+ "rm -rf $(genDir)/content ",
+ visibility: ["//visibility:private"],
+}
diff --git a/hostsidetests/library/AndroidTest.xml b/hostsidetests/library/AndroidTest.xml
new file mode 100644
index 0000000..bd7119c
--- /dev/null
+++ b/hostsidetests/library/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 uses-native-library tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsUsesNativeLibraryTest.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/library/OWNERS b/hostsidetests/library/OWNERS
new file mode 100644
index 0000000..ac8666c
--- /dev/null
+++ b/hostsidetests/library/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 87896
+jiyong@google.com
+
diff --git a/hostsidetests/library/res/AndroidManifest_template.xml b/hostsidetests/library/res/AndroidManifest_template.xml
new file mode 100644
index 0000000..ee0336f
--- /dev/null
+++ b/hostsidetests/library/res/AndroidManifest_template.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<!-- This file is a template. Strings surrounded by % characters are to be replaced -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.usesnativesharedlibrary">
+
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="%TARGET_SDK_VERSION%" />
+
+ <application>
+ <!-- This java library is required for the test itself -->
+ <uses-library android:name="android.test.runner" />
+ <!-- Dependencies to the native shared libraries come here -->
+ %USES_LIBRARY%
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.test.usesnativesharedlibrary"
+ android:label="NativeLibraryLoadTest" />
+</manifest>
diff --git a/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
new file mode 100644
index 0000000..27bfe15
--- /dev/null
+++ b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
@@ -0,0 +1,499 @@
+/*
+ * 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 android.appmanifest.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import com.android.tradefed.device.IFileEntry;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.BufferedWriter;
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+/**
+ * Tests about uses-native-library tags that was introduced in Android S.
+ *
+ * The test reads the list of partner-defined public native shared libraries
+ * (see <a href="https://source.android.com/devices/tech/config/namespaces_libraries#adding-additional-native-libraries)">
+ * Adding additional native libraries</a>) and make sure that those are available to the apps
+ * only when they are explicitly listed on the app manifest using the new tag. The libs not listed
+ * are not available even though they are declared as public.
+ *
+ * This test also make sure that the new behavior is only for the new apps targeting Android S or
+ * higher. Apps targeting Android 11 or lower still has access to all partner-defined public libs
+ * regardless of the use of the tag.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UsesNativeLibraryTestCase extends BaseHostJUnit4Test {
+ // The list of partner-defined public native shared libraries
+ private final Set<String> mPublicLibraries = new HashSet<>();
+
+ // The list of public libs that we will make the test app to depend on
+ private final Set<String> mSomePublicLibraries = new HashSet<>();
+
+ // Remaining public libraries that shouldn't be available to new apps
+ private final Set<String> mRemainingPublicLibraries = new HashSet<>();
+
+ private File mWorkDir;
+
+ // Name of a fake library that doesn't exist on the device
+ private String mNonExistingLib;
+
+ // Name of a library that actually exists on the device, but is not part of the public libraries
+ private String mPrivateLib;
+
+ @Before
+ public void setUp() throws Exception {
+ // extract "foo.so" from lines of foo.so -> (so) foo.so
+ Pattern pattern = Pattern.compile("(\\S+)\\s*->\\s*\\((\\S+)\\)\\s*(\\S+)");
+ Arrays.stream(executeShellCommand("dumpsys package libraries").split("\n")).
+ skip(1) /* for "Libraries:" header */ .
+ map(line -> pattern.matcher(line.trim())).
+ filter(matcher -> matcher.matches() && matcher.group(2).equals("so")).
+ map(matcher -> matcher.group(1)).
+ forEach(mPublicLibraries::add);
+
+ // Pick first half of the public libraries
+ mPublicLibraries.stream().
+ limit(mPublicLibraries.size() / 2).
+ forEach(mSomePublicLibraries::add);
+
+ // ... and remainders
+ mPublicLibraries.stream().
+ filter(lib -> !mSomePublicLibraries.contains(lib)).
+ forEach(mRemainingPublicLibraries::add);
+
+ mNonExistingLib = "libnamethatneverexist.so";
+ assertFalse(mPublicLibraries.contains(mNonExistingLib)); // unlikely!
+
+ mPrivateLib = "libui.so"; // randomly chosen private lib
+ assertTrue(getDevice().getFileEntry("/system/lib/" + mPrivateLib) != null ||
+ getDevice().getFileEntry("/system/lib64/" + mPrivateLib) != null);
+ assertFalse(mPublicLibraries.contains(mPrivateLib));
+
+ // The zip file contains all the tools and files for building a test app on demand. Extract
+ // it to the work directory.
+ try (ZipFile packageZip = new ZipFile(getTestInformation().getDependencyFile(
+ "CtsUesNativeLibraryBuildPackage.zip", false))) {
+ mWorkDir = FileUtil.createTempDir("work");
+ ZipUtil.extractZip(packageZip, mWorkDir);
+
+ // Make sure executables are executable
+ FileUtil.chmod(getFile("aapt2"), "u+x");
+ FileUtil.chmod(getFile("merge_zips"), "u+x");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void cleanUp() {
+ FileUtil.recursiveDelete(mWorkDir);
+ }
+
+ private File getFile(String path) {
+ return new File(mWorkDir, path);
+ }
+
+ private String executeShellCommand(String command) {
+ try {
+ return getDevice().executeShellCommand(command);
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Stream<IFileEntry> getFileEntriesUnder(String path) {
+ try {
+ return getDevice().getFileEntry(path).getChildren(true).stream();
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Stream<String> findPublicLibraryFilesUnder(String partition) {
+ return getFileEntriesUnder(partition + "/etc").
+ filter(fe -> {
+ // For vendor partition we only allow public.libraries.txt file.
+ // For other partitions, partner-added libs can be listed in
+ // public.libraries-<companyname>.txt files.
+ if (partition.equals("/vendor")) {
+ return fe.getName().equals("public.libraries.txt");
+ } else {
+ return fe.getName().startsWith("public.libraries-") &&
+ fe.getName().endsWith(".txt");
+ }
+ }).
+ map(fe -> fe.getFullPath());
+ }
+
+ /**
+ * Tests if the native shared library list reported by the package manager is the same as
+ * the public.libraries*.txt files in the partitions.
+ */
+ @Test
+ public void testPublicLibrariesAreAllRegistered() throws DeviceNotAvailableException {
+ Set<String> libraryNamesFromTxt =
+ Stream.of("/system", "/system_ext", "/product", "/vendor").
+ flatMap(dir -> findPublicLibraryFilesUnder(dir)).
+ map(file -> executeShellCommand("cat " + file)).
+ flatMap(lines -> Arrays.stream(lines.split("\n"))).
+ filter(line -> {
+ // filter-out empty lines or comment lines that start with #
+ String strip = line.strip();
+ return !strip.isEmpty() && !strip.startsWith("#");
+ }).
+ // line format is "name [bitness]". Extract the name part.
+ map(line -> line.strip().split("\\s+")[0]).
+ collect(Collectors.toSet());
+
+ assertEquals(mPublicLibraries, libraryNamesFromTxt);
+ }
+
+ /**
+ * Creates an AndroidManifest.xml file from the template with the given api level and the list
+ * of mandatory and optional native shared libraries
+ */
+ private File createManifestFileWithUsesNativeLibraryTags(File dir, int apiLevel,
+ String[] requiredLibraries, String[] optionalLibraries) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ getClass().getClassLoader().
+ getResourceAsStream("AndroidManifest_template.xml")))) {
+ StringBuffer sb = new StringBuffer();
+ String line = null;
+ while( (line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ String template = sb.toString();
+
+ sb = new StringBuffer();
+ for(String lib : requiredLibraries) {
+ sb.append(String.format(
+ "<uses-native-library android:name=\"%s\"/>", lib));
+ }
+ for(String lib : optionalLibraries) {
+ sb.append(String.format(
+ "<uses-native-library android:name=\"%s\" android:required=\"false\"/>",
+ lib));
+ }
+
+ String newContent = template.replace("%USES_LIBRARY%", sb.toString());
+ newContent = newContent.replace("%TARGET_SDK_VERSION%", Integer.toString(apiLevel));
+
+ File output = new File(dir, "AndroidManifest.xml");
+ FileUtil.writeToFile(newContent, output);
+ return output;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void runCommand(String cmd) {
+ CommandResult result = RunUtil.getDefault().runTimedCmd(100000, cmd.split(" "));
+ if (result.getExitCode() != 0) {
+ throw new RuntimeException(result.getStderr());
+ }
+ }
+
+ private File buildTestApp(int apiLevel,
+ String[] requiredLibraries,
+ String[] optionalLibraries,
+ String[] availableLibraries,
+ String[] unavailableLibraries) throws IOException {
+ File buildRoot = FileUtil.createTempDir("appbuild", mWorkDir);
+
+ // Create available.txt and unavailable.txt files. They contain the list of native libs
+ // that must be loadable and non-loadable. The Test app will fail if any of the lib in
+ // available.txt is non-loadable, or if any of the lib in unavailable.txt is loadable.
+ File assetDir = new File(buildRoot, "asset");
+ assetDir.mkdir();
+ File availableTxtFile = new File(assetDir, "available.txt");
+ File unavailableTxtFile = new File(assetDir, "unavailable.txt");
+ FileUtil.writeToFile(String.join("\n", availableLibraries), availableTxtFile, false);
+ FileUtil.writeToFile(String.join("\n", unavailableLibraries), unavailableTxtFile, false);
+
+ File manifestFile = createManifestFileWithUsesNativeLibraryTags(buildRoot, apiLevel,
+ requiredLibraries, optionalLibraries);
+
+ File resFile = new File(buildRoot, "package-res.apk");
+ runCommand(String.format("%s link --manifest %s -I %s -A %s -o %s",
+ getFile("aapt2"),
+ manifestFile,
+ getFile("android.jar"),
+ assetDir,
+ resFile));
+
+ // Append the app code to the apk
+ File unsignedApkFile = new File(buildRoot, "unsigned.apk");
+ runCommand(String.format("%s %s %s %s",
+ getFile("merge_zips"),
+ unsignedApkFile,
+ resFile,
+ getFile("CtsUsesNativeLibraryTestApp.jar")));
+
+ File signedApkFile = new File(buildRoot, "signed.apk");
+ runCommand(String.format("java -Djava.library.path=%s -jar %s %s %s %s %s",
+ mWorkDir,
+ getFile("signapk.jar"),
+ getFile("testkey.x509.pem"),
+ getFile("testkey.pk8"),
+ unsignedApkFile,
+ signedApkFile));
+
+ return signedApkFile;
+ }
+
+ private boolean installTestApp(File testApp) throws Exception {
+ // Explicit uninstallation is required because we might downgrade the target API level
+ // from 31 to 30
+ uninstallPackage("com.android.test.usesnativesharedlibrary");
+ try {
+ installPackage(testApp.toString());
+ return true;
+ } catch (TargetSetupError e) {
+ System.out.println(e.getMessage());
+ return false;
+ }
+ }
+
+ private void runInstalledTestApp() throws Exception {
+ runDeviceTests("com.android.test.usesnativesharedlibrary",
+ "com.android.test.usesnativesharedlibrary.LoadTest");
+ }
+
+ private static String[] add(Set<String> s, String...extra) {
+ List<String> ret = new ArrayList<>();
+ ret.addAll(s);
+ ret.addAll(Arrays.asList(extra));
+ return ret.toArray(new String[0]);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Tests for when apps depend on non-existing lib
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void testOldAppDependsOnNonExistingLib() throws Exception {
+ String[] requiredLibs = {mNonExistingLib};
+ String[] optionalLibs = {};
+ String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+ String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(30,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppDependsOnNonExistingLib() throws Exception {
+ String[] requiredLibs = {mNonExistingLib};
+ String[] optionalLibs = {};
+ String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+ String[] unavailableLibs = add(mPublicLibraries, mNonExistingLib, mPrivateLib);
+
+ assertFalse(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+
+ // install failed, so can't run the on-device test
+ }
+
+ @Test
+ public void testOldAppOptionallyDependsOnNonExistingLib() throws Exception {
+ String[] requiredLibs = {};
+ String[] optionalLibs = {mNonExistingLib};
+ String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+ String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(30,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppOptionallyDependsOnNonExistingLib() throws Exception {
+ String[] requiredLibs = {};
+ String[] optionalLibs = {mNonExistingLib};
+ String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+ String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Tests for when apps depend on private lib
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void testOldAppDependsOnPrivateLib() throws Exception {
+ String[] requiredLibs = {mPrivateLib};
+ String[] optionalLibs = {};
+ String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+ String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(30,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppDependsOnPrivateLib() throws Exception {
+ String[] requiredLibs = {mPrivateLib};
+ String[] optionalLibs = {};
+ String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+ String[] unavailableLibs = add(mPublicLibraries, mPrivateLib, mPrivateLib);
+
+ assertFalse(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+
+ // install failed, so can't run the on-device test
+ }
+
+ @Test
+ public void testOldAppOptionallyDependsOnPrivateLib() throws Exception {
+ String[] requiredLibs = {};
+ String[] optionalLibs = {mPrivateLib};
+ String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+ String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(30,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppOptionallyDependsOnPrivateLib() throws Exception {
+ String[] requiredLibs = {};
+ String[] optionalLibs = {mPrivateLib};
+ String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+ String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Tests for when apps depend on all public libraries
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void testOldAppDependsOnAllPublicLibraries() throws Exception {
+ String[] requiredLibs = add(mPublicLibraries);
+ String[] optionalLibs = {};
+ String[] availableLibs = add(mPublicLibraries); // old app still has access to all libs
+ String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(30,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppDependsOnAllPublicLibraries() throws Exception {
+ String[] requiredLibs = add(mPublicLibraries);
+ String[] optionalLibs = {};
+ String[] availableLibs = add(mPublicLibraries); // new app now has access to all libs
+ String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Tests for when apps depend on some public libraries
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void testOldAppDependsOnSomePublicLibraries() throws Exception {
+ // select the first half of the public lib
+ String[] requiredLibs = add(mSomePublicLibraries);
+ String[] optionalLibs = {};
+ String[] availableLibs = add(mPublicLibraries); // old app still has access to all libs
+ String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+ assertTrue(installTestApp(buildTestApp(30,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppDependsOnSomePublicLibraries() throws Exception {
+ String[] requiredLibs = add(mSomePublicLibraries);
+ String[] optionalLibs = {};
+ // new app has access to the listed libs only
+ String[] availableLibs = add(mSomePublicLibraries);
+ // And doesn't have access to the remaining public libs and of course non-existing
+ // and private libs.
+ String[] unavailableLibs = add(mRemainingPublicLibraries, mNonExistingLib, mPrivateLib);
+
+ assertTrue(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+
+ @Test
+ public void testNewAppOptionallyDependsOnSomePublicLibraries() throws Exception {
+ // select the first half of the public lib
+ String[] requiredLibs = {};
+ String[] optionalLibs = add(mSomePublicLibraries);
+ // new app has access to the listed libs only
+ String[] availableLibs = add(mSomePublicLibraries);
+ // And doesn't have access to the remaining public libs and of course non-existing
+ // and private libs.
+ String[] unavailableLibs = add(mRemainingPublicLibraries, mNonExistingLib, mPrivateLib);
+
+ assertTrue(installTestApp(buildTestApp(31,
+ requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+ runInstalledTestApp();
+ }
+}
+
diff --git a/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java b/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java
new file mode 100644
index 0000000..b77af78
--- /dev/null
+++ b/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 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.test.usesnativesharedlibrary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Tests if native shared libs are loadable or un-loadable as expected. The list of loadable libs is
+ * in the asset file <code>available.txt</code> and the list of un-loadable libs is in the asset
+ * file <code>unavailable.txt</code>. The files are dynamically created by the host-side test
+ * <code>UsesNativeLibraryTestCase</code>.
+ */
+@RunWith(JUnit4.class)
+public class LoadTest {
+ private List<String> libNamesFromAssetFile(String filename) {
+ List<String> result = new ArrayList<>();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ ApplicationProvider.getApplicationContext().getAssets().open(filename)))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.isEmpty() && line.startsWith("lib") && line.endsWith(".so")) {
+ // libfoo.so -> foo because that's what System.loadLibrary accepts
+ result.add(line.substring(3, line.length()-3));
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return result;
+ }
+
+ /**
+ * Tests if libs listed in available.txt are all loadable
+ */
+ @Test
+ public void testAvailableLibrariesAreLoaded() {
+ List<String> unexpected = new ArrayList<>();
+ for (String lib : libNamesFromAssetFile("available.txt")) {
+ try {
+ System.loadLibrary(lib);
+ } catch (Throwable t) {
+ unexpected.add(t.getMessage());
+ }
+ };
+ assertThat("Some libraries failed to load", unexpected, is(Collections.emptyList()));
+ }
+
+ /**
+ * Tests if libs listed in unavailable.txt are all non-loadable
+ */
+ @Test
+ public void testUnavailableLibrariesAreNotLoaded() {
+ List<String> loadedLibs = new ArrayList<>();
+ List<String> unexpectedFailures = new ArrayList<>();
+ for (String lib : libNamesFromAssetFile("unavailable.txt")) {
+ try {
+ System.loadLibrary(lib);
+ loadedLibs.add("lib" + lib + ".so");
+ } catch (UnsatisfiedLinkError e) {
+ // This is expected
+ } catch (Throwable t) {
+ unexpectedFailures.add(t.getMessage());
+ }
+ };
+ assertThat("Some unavailable libraries were loaded", loadedLibs, is(Collections.emptyList()));
+ assertThat("Unexpected errors occurred", unexpectedFailures, is(Collections.emptyList()));
+ }
+}
diff --git a/hostsidetests/library/testkey.pk8 b/hostsidetests/library/testkey.pk8
new file mode 100644
index 0000000..586c1bd
--- /dev/null
+++ b/hostsidetests/library/testkey.pk8
Binary files differ
diff --git a/hostsidetests/library/testkey.x509.pem b/hostsidetests/library/testkey.x509.pem
new file mode 100644
index 0000000..e242d83
--- /dev/null
+++ b/hostsidetests/library/testkey.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
+J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
+LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
+31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
+sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
+-----END CERTIFICATE-----