RESTRICT AUTOMERGE Do not test the ELF objects not built for Android am: 9f34c6128a

Original change: https://android-review.googlesource.com/c/platform/test/vts-testcase/vndk/+/2923412

Change-Id: I37405f8ef5b0bbea4bdb5f9169e072f658053f41
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index d7b26f9..ccae16d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -16,8 +16,9 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-python_library_host {
+python_library {
     name: "vts_vndk_utils",
+    host_supported: true,
     pkg_path: "vts/testcases/vndk",
     srcs: [
         "utils.py",
@@ -42,6 +43,14 @@
     }
 }
 
+// TODO(b/243602514): Python data should not be put in testcases dir.
+python_library_host {
+    name: "vts_vndk_abi_dump_lib",
+    data: [
+        ":vts_vndk_abi_dump_zip",
+    ],
+}
+
 python_test_host {
     name: "vts_vndk_abi_test",
     defaults: ["vts_vndk_default"],
@@ -49,8 +58,8 @@
     srcs: [
         "abi/vts_vndk_abi_test.py",
     ],
-    data: [
-        ":vts_vndk_abi_dump_zip",
+    libs: [
+        "vts_vndk_abi_dump_lib",
     ],
     test_suites: [
         "vts",
@@ -61,8 +70,12 @@
     },
 }
 
-python_test_host {
+python_test {
     name: "vts_vndk_dependency_test",
+    // vts_vndk_dependency_test.xml refers to the file name. It needs to be
+    // different from the directory name so that the test runner can find a
+    // unique path.
+    stem: "vts_vndk_dependency_test_bin",
     defaults: ["vts_vndk_default"],
     main: "dependency/vts_vndk_dependency_test.py",
     srcs: [
diff --git a/abi/vts_vndk_abi_test.py b/abi/vts_vndk_abi_test.py
index 386e812..a73a23f 100644
--- a/abi/vts_vndk_abi_test.py
+++ b/abi/vts_vndk_abi_test.py
@@ -307,6 +307,9 @@
         Args:
             bitness: 32 or 64, the bitness of the tested libraries.
         """
+        if not vndk_utils.IsVndkRequired(self._dut):
+            logging.info("Skip the test as the device does not require VNDK.")
+            return
         self.assertTrue(self._dut.IsRoot(), "This test requires adb root.")
         primary_abi = self._dut.GetCpuAbiList()[0]
         binder_bitness = self._dut.GetBinderBitness()
diff --git a/dependency/vts_vndk_dependency_test.py b/dependency/vts_vndk_dependency_test.py
index f11838c..c7aeebc 100644
--- a/dependency/vts_vndk_dependency_test.py
+++ b/dependency/vts_vndk_dependency_test.py
@@ -18,11 +18,8 @@
 import collections
 import logging
 import os
-import posixpath as target_path_module
 import re
-import shutil
 import sys
-import tempfile
 import unittest
 
 from vts.testcases.vndk import utils
@@ -36,8 +33,6 @@
 
     Attributes:
         _dut: The AndroidDevice under test.
-        _temp_dir: The temporary directory to which the odm and vendor
-                   partitions are copied.
         _vndk_version: The VNDK version of the device.
         _ll_ndk: Set of strings. The names of low-level NDK libraries in
                  /system/lib[64].
@@ -45,6 +40,7 @@
                  expected to be in /vendor/lib[64].
         _vndk: Set of strings. The names of VNDK-core libraries.
         _vndk_sp: Set of strings. The names of VNDK-SP libraries.
+        _VENDOR_DIRS: The directories of vendor partitions.
         _SP_HAL_LINK_PATHS: Format strings of same-process HAL's default link
                             paths.
         _VENDOR_LINK_PATHS: Format strings of vendor processes' default link
@@ -53,11 +49,9 @@
                                  permitted link paths.
         _VENDOR_APP_DIRS: The app directories in vendor partitions.
     """
-    _TARGET_DIR_SEP = "/"
-    _TARGET_ROOT_DIR = "/"
-    _TARGET_ODM_DIR = "/odm"
-    _TARGET_VENDOR_DIR = "/vendor"
-
+    _VENDOR_DIRS = [
+        "/odm", "/vendor"
+    ]
     _SP_HAL_LINK_PATHS = [
         "/odm/{LIB}/egl", "/odm/{LIB}/hw", "/odm/{LIB}",
         "/vendor/{LIB}/egl", "/vendor/{LIB}/hw", "/vendor/{LIB}"
@@ -93,8 +87,8 @@
         def __init__(self, target_path, bitness, deps, runpaths,
                      custom_link_paths):
             self.target_path = target_path
-            self.name = target_path_module.basename(target_path)
-            self.target_dir = target_path_module.dirname(target_path)
+            self.name = os.path.basename(target_path)
+            self.target_dir = os.path.dirname(target_path)
             self.bitness = bitness
             self.deps = deps
             # Format runpaths
@@ -109,20 +103,10 @@
             self.custom_link_paths = custom_link_paths
 
     def setUp(self):
-        """Initializes device, temporary directory, and VNDK lists."""
-        serial_number = os.environ.get("ANDROID_SERIAL")
-        self.assertTrue(serial_number, "$ANDROID_SERIAL is empty.")
-        self._dut = utils.AndroidDevice(serial_number)
+        """Initializes VNDK lists."""
+        self._dut = utils.AndroidDevice()
         self.assertTrue(self._dut.IsRoot(), "This test requires adb root.")
 
-        self._temp_dir = tempfile.mkdtemp()
-        for target_dir in (self._TARGET_ODM_DIR, self._TARGET_VENDOR_DIR):
-            if self._dut.IsDirectory(target_dir):
-                logging.info("adb pull %s %s", target_dir, self._temp_dir)
-                self._dut.AdbPull(target_dir, self._temp_dir)
-            else:
-                logging.info("Skip adb pull %s", target_dir)
-
         self._vndk_version = self._dut.GetVndkVersion()
         vndk_lists = vndk_data.LoadVndkLibraryListsFromResources(
             self._vndk_version,
@@ -134,22 +118,21 @@
 
         sp_hal_strings = vndk_lists[0]
         self._sp_hal = [re.compile(x) for x in sp_hal_strings]
-        self._ll_ndk = vndk_lists[1]
-        if vndk_utils.IsVndkInstalledInVendor(self._dut):
-            (self._vndk, self._vndk_sp) = ([], [])
+        if vndk_utils.IsVndkRequired(self._dut):
+            self._ll_ndk = vndk_lists[1]
+            if vndk_utils.IsVndkInstalledInVendor(self._dut):
+                (self._vndk, self._vndk_sp) = ([], [])
+            else:
+                (self._vndk, self._vndk_sp) = vndk_lists[2:]
         else:
-            (self._vndk, self._vndk_sp) = vndk_lists[2:]
+            self._ll_ndk = self._dut.GetLlndkList()
+            (self._vndk, self._vndk_sp) = ([], [])
 
         logging.debug("LL_NDK: %s", self._ll_ndk)
         logging.debug("SP_HAL: %s", sp_hal_strings)
         logging.debug("VNDK: %s", self._vndk)
         logging.debug("VNDK_SP: %s", self._vndk_sp)
 
-    def tearDown(self):
-        """Deletes the temporary directory."""
-        logging.info("Delete %s", self._temp_dir)
-        shutil.rmtree(self._temp_dir, ignore_errors=True)
-
     def _IsElfObjectForAp(self, elf, target_path, abi_list):
         """Checks whether an ELF object is for application processor.
 
@@ -219,26 +202,24 @@
         return True
 
     @staticmethod
-    def _IterateFiles(host_dir):
-        """Iterates files in a host directory.
+    def _IterateFiles(target_dir):
+        """Iterates files in a directory.
 
         Args:
-            host_dir: The host directory.
+            target_dir: The directory.
 
         Yields:
             The file paths under the directory.
         """
-        for root_dir, dir_names, file_names in os.walk(host_dir):
+        for root_dir, dir_names, file_names in os.walk(target_dir):
             for file_name in file_names:
                 yield os.path.join(root_dir, file_name)
 
-    def _LoadElfObjects(self, host_dir, target_dir, abi_list,
-                        elf_error_handler):
-        """Scans a host directory recursively and loads all ELF files in it.
+    def _LoadElfObjects(self, target_dir, abi_list, elf_error_handler):
+        """Scans a directory recursively and loads all ELF files in it.
 
         Args:
-            host_dir: The host directory to scan.
-            target_dir: The path from which host_dir is copied.
+            target_dir: The directory to scan.
             abi_list: A list of strings, the ABIs of the ELF files to load.
             elf_error_handler: A function that takes 2 arguments
                                (target_path, exception). It is called when
@@ -248,12 +229,9 @@
             List of ElfObject.
         """
         objs = []
-        for full_path in self._IterateFiles(host_dir):
-            rel_path = os.path.relpath(full_path, host_dir)
-            target_path = target_path_module.join(
-                target_dir, *rel_path.split(os.path.sep))
+        for target_path in self._IterateFiles(target_dir):
             try:
-                elf = elf_parser.ElfParser(full_path)
+                elf = elf_parser.ElfParser(target_path)
             except elf_parser.ElfError:
                 logging.debug("%s is not an ELF file", target_path)
                 continue
@@ -279,10 +257,9 @@
 
             # b/123216664 App libraries depend on those in the same directory.
             custom_link_paths = []
-            if any(target_path.startswith(app_dir + self._TARGET_DIR_SEP) for
+            if any(target_path.startswith(app_dir + os.path.sep) for
                     app_dir in self._VENDOR_APP_DIRS):
-                custom_link_paths.append(
-                    target_path_module.dirname(target_path))
+                custom_link_paths.append(os.path.dirname(target_path))
 
             objs.append(self.ElfObject(target_path, elf.bitness, deps,
                                        runpaths, custom_link_paths))
@@ -304,8 +281,7 @@
         namespace = collections.defaultdict(dict)
         for obj in objs:
             if (obj.bitness == bitness and
-                    any(obj.target_path.startswith(link_path +
-                                                   self._TARGET_DIR_SEP)
+                    any(obj.target_path.startswith(link_path + os.path.sep)
                         for link_path in link_paths)):
                 namespace[obj.target_dir][obj.name] = obj
         return namespace
@@ -392,7 +368,7 @@
 
         if vndk_utils.IsVndkInstalledInVendor(self._dut):
             vndk_in_vendor = [
-                target_path_module.basename(lib_path) for lib_path in
+                os.path.basename(lib_path) for lib_path in
                 self._dut.FindFiles(
                     vndk_utils.GetVndkDirectory(bitness, self._vndk_version),
                     "*", "!", "-type", "d")]
@@ -467,9 +443,11 @@
         """Tests vendor libraries/executables and SP-HAL dependencies."""
         read_errors = []
         abi_list = self._dut.GetCpuAbiList()
-        objs = self._LoadElfObjects(
-            self._temp_dir, self._TARGET_ROOT_DIR, abi_list,
-            lambda p, e: read_errors.append((p, str(e))))
+        objs = []
+        for target_dir in self._VENDOR_DIRS:
+            objs += self._LoadElfObjects(
+                target_dir, abi_list,
+                lambda p, e: read_errors.append((p, str(e))))
 
         dep_errors = self._TestElfDependency(32, objs)
         if self._dut.GetCpuAbiList(64):
@@ -498,9 +476,6 @@
 
 
 if __name__ == "__main__":
-    # The logs are written to stdout so that TradeFed test runner can parse the
-    # results from stderr.
+    # Write logs to stdout and results to stderr.
     logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
-    # Setting verbosity is required to generate output that the TradeFed test
-    # runner can parse.
     unittest.main(verbosity=3)
diff --git a/dependency/vts_vndk_dependency_test.xml b/dependency/vts_vndk_dependency_test.xml
index 0a15678..7c8868e 100644
--- a/dependency/vts_vndk_dependency_test.xml
+++ b/dependency/vts_vndk_dependency_test.xml
@@ -15,9 +15,16 @@
 -->
 <configuration description="Config for vts_vndk_dependency_test">
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
-    <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest">
-        <option name="par-file-name" value="vts_vndk_dependency_test" />
-        <option name="test-timeout" value="10m" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer" >
+        <option name="push-file" key="vts_vndk_dependency_test_bin" value="/data/local/tmp/vts_vndk_dependency_test" />
+        <option name="abort-on-push-failure" value="true" />
+        <option name="cleanup" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest">
+        <option name="binary" value="/data/local/tmp/vts_vndk_dependency_test" />
+        <option name="per-binary-timeout" value="3m" />
     </test>
 </configuration>
 
diff --git a/files/vts_vndk_files_test.py b/files/vts_vndk_files_test.py
index 24f2d82..9c1510f 100644
--- a/files/vts_vndk_files_test.py
+++ b/files/vts_vndk_files_test.py
@@ -101,28 +101,24 @@
                        "The above libraries are not %s." %
                        ", ".join(vndk_list_names))
 
-    def _TestNotInVndkDirecotory(self, vndk_dir, vndk_list_names, except_libs):
-        """Verifies that VNDK directory doesn't contain specific files.
+    def _TestNoLlndkInDirectory(self, lib_dir):
+        """Verifies that the vendor directory doesn't contain LLNDK libraries.
 
         Args:
-            vndk_dir, The path to the VNDK directory on device.
-            vndk_list_names: A list of strings, the categories of the VNDK
-                             libraries that should not be in the directory.
-            except_libs: A set of strings, the file names of the libraries that
-                         are exceptions to this test.
+            lib_dir: The path to the directory on device.
         """
-        vndk_lists = vndk_data.LoadVndkLibraryListsFromResources(
-            self._vndk_version, *vndk_list_names)
-        self.assertTrue(vndk_lists, "Cannot load VNDK library lists.")
-        vndk_set = set().union(*vndk_lists)
-        vndk_set.difference_update(except_libs)
-        logging.debug("vndk set: %s", vndk_set)
-        unexpected = [x for x in self._ListFiles(vndk_dir) if
-                      target_path_module.basename(x) in vndk_set]
+        if vndk_utils.IsVndkRequired(self._dut):
+            llndk_list = vndk_data.LoadVndkLibraryListsFromResources(
+                self._vndk_version, vndk_data.LL_NDK)[0]
+        else:
+            llndk_list = self._dut.GetLlndkList()
+        llndk_set = set(llndk_list).difference(self._LL_NDK_COLLIDING_NAMES)
+        logging.debug("llndk set: %s", llndk_set)
+        unexpected = [x for x in self._ListFiles(lib_dir) if
+                      target_path_module.basename(x) in llndk_set]
         if unexpected:
             self._Fail(unexpected,
-                       "%s must not contain %s libraries." %
-                       (vndk_dir, ", ",join(vndk_list_names)))
+                       lib_dir + " must not contain LLNDK libraries.")
 
     def _TestVndkCoreDirectory(self, bitness):
         """Verifies that VNDK directory doesn't contain extra files."""
@@ -130,6 +126,9 @@
             logging.info("Skip the test as VNDK runtime is not enforced on "
                          "the device.")
             return
+        if not vndk_utils.IsVndkRequired(self._dut):
+            logging.info("Skip the test as the device does not require VNDK.")
+            return
         if vndk_utils.IsVndkInstalledInVendor(self._dut):
             logging.info("Skip the test as VNDK %s should be installed in "
                          "vendor partition.", self._vndk_version)
@@ -153,10 +152,8 @@
 
     def _TestNoLlndkInVendor(self, bitness):
         """Verifies that vendor partition has no LL-NDK libraries."""
-        self._TestNotInVndkDirecotory(
-            vndk_utils.FormatVndkPath(self._TARGET_VENDOR_LIB, bitness),
-            (vndk_data.LL_NDK,),
-            self._LL_NDK_COLLIDING_NAMES)
+        self._TestNoLlndkInDirectory(
+            vndk_utils.FormatVndkPath(self._TARGET_VENDOR_LIB, bitness))
 
     def testNoLlndkInVendor32(self):
         """Runs _TestNoLlndkInVendor for 32-bit libraries."""
@@ -172,10 +169,8 @@
 
     def _TestNoLlndkInOdm(self, bitness):
         """Verifies that odm partition has no LL-NDK libraries."""
-        self._TestNotInVndkDirecotory(
-            vndk_utils.FormatVndkPath(self._TARGET_ODM_LIB, bitness),
-            (vndk_data.LL_NDK,),
-            self._LL_NDK_COLLIDING_NAMES)
+        self._TestNoLlndkInDirectory(
+            vndk_utils.FormatVndkPath(self._TARGET_ODM_LIB, bitness))
 
     def testNoLlndkInOdm32(self):
         """Runs _TestNoLlndkInOdm for 32-bit libraries."""
diff --git a/golden/extract_lsdump.py b/golden/extract_lsdump.py
index 8849d48..fe70be4 100755
--- a/golden/extract_lsdump.py
+++ b/golden/extract_lsdump.py
@@ -284,10 +284,9 @@
     #                    the large file group.
     if zipfile.is_zipfile(args.input_path):
         with zipfile.ZipFile(args.input_path, mode='r') as input_zip:
-            # The zip will be added to a Python package. It is not necessary
-            # to reduce the file size.
-            with zipfile.ZipFile(args.output_path, mode='w',
-                                 compression=zipfile.ZIP_STORED) as output_zip:
+            with zipfile.ZipFile(
+                    args.output_path, mode='w',
+                    compression=zipfile.ZIP_DEFLATED) as output_zip:
                 _ParseLsdumpZipFile(input_zip, output_zip)
         sys.exit(0)
 
diff --git a/golden/vndk_data.py b/golden/vndk_data.py
index a35c1c0..bb1a8d7 100644
--- a/golden/vndk_data.py
+++ b/golden/vndk_data.py
@@ -79,8 +79,8 @@
         self.zip_file = None
 
     def __enter__(self):
-        self._resource = resources.open_binary(_RESOURCE_PACKAGE,
-                                               _ABI_DUMP_ZIP_NAME)
+        self._resource = resources.files(_RESOURCE_PACKAGE).joinpath(
+            _ABI_DUMP_ZIP_NAME).open("rb")
         self.zip_file = zipfile.ZipFile(self._resource, "r")
         return self
 
@@ -137,32 +137,51 @@
     return dump_paths
 
 
-def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file):
+def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file,
+                              change_history_file=None):
     """Load VNDK libraries from the file to the specified tuple.
 
     Args:
         vndk_lists: The output tuple of lists containing library names.
         tags: Strings, the tags of the libraries to find.
         vndk_lib_list_file: The file object containing the VNDK library list.
+        change_history_file: The file object containing the VNDK list change
+            history. It adds the vndk files that are removed.
     """
+    def ReadTagAndFile(line):
+        # Ignore comments.
+        if line.startswith('#'):
+            return None, None
+
+        # Split columns.
+        cells = line.split(': ', 1)
+        if len(cells) < 2:
+            return None, None
+        return cells[0].strip(), cells[1].strip()
 
     lib_sets = collections.defaultdict(set)
 
     # Load VNDK tags from the list.
     for line in vndk_lib_list_file:
-        # Ignore comments.
-        if line.startswith('#'):
+        tag, lib_name = ReadTagAndFile(line)
+        if not tag:
             continue
-
-        # Split columns.
-        cells = line.split(': ', 1)
-        if len(cells) < 2:
-            continue
-        tag = cells[0]
-        lib_name = cells[1].strip()
-
         lib_sets[tag].add(lib_name)
 
+    if change_history_file:
+        for line in change_history_file:
+            tag, lib_name = ReadTagAndFile(line)
+            if not tag:
+                continue
+
+            # In the history file, tag has '+' prefix if the file is added and
+            # '-' prefix if removed.
+            # To relax the test, include the removed files to the list.
+            if tag[0] != '-':
+                continue
+            tag = tag[1:]
+            lib_sets[tag].add(lib_name)
+
     # Compute VNDK-core-private and VNDK-SP-private.
     private = lib_sets.get('VNDK-private', set())
 
@@ -202,20 +221,31 @@
     version_str = (version if re.match("\\d+", version) and int(version) <= 34
                    else "current")
     vndk_lib_list_name = version_str + ".txt"
+    vndk_lib_list = resources.files(_RESOURCE_PACKAGE).joinpath(
+        vndk_lib_list_name)
+    vndk_lib_list_history_name = version_str + "_history.txt"
+    vndk_lib_list_history = resources.files(_RESOURCE_PACKAGE).joinpath(
+        vndk_lib_list_history_name)
     vndk_lib_extra_list_name = "vndk-lib-extra-list-" + version_str + ".txt"
+    vndk_lib_extra_list = resources.files(_RESOURCE_PACKAGE).joinpath(
+        vndk_lib_extra_list_name)
 
-    if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_list_name):
+    if not vndk_lib_list.is_file():
         logging.warning("Cannot load %s.", vndk_lib_list_name)
         return None
 
-    if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_extra_list_name):
+    if not vndk_lib_extra_list.is_file():
         logging.warning("Cannot load %s.", vndk_lib_extra_list_name)
         return None
 
     vndk_lists = tuple([] for x in tags)
 
-    with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_list_name) as f:
-        _LoadVndkLibraryListsFile(vndk_lists, tags, f)
-    with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_extra_list_name) as f:
+    with vndk_lib_list.open("r") as f:
+        if vndk_lib_list_history.is_file():
+            with vndk_lib_list_history.open("r") as history:
+                _LoadVndkLibraryListsFile(vndk_lists, tags, f, history)
+        else:
+            _LoadVndkLibraryListsFile(vndk_lists, tags, f)
+    with vndk_lib_extra_list.open("r") as f:
         _LoadVndkLibraryListsFile(vndk_lists, tags, f)
     return vndk_lists
diff --git a/utils.py b/utils.py
index 8bf00f3..36814ab 100644
--- a/utils.py
+++ b/utils.py
@@ -15,32 +15,50 @@
 # limitations under the License.
 #
 
-# TODO(b/147454897): Keep the logic in sync with
-#                    test/vts/utils/python/controllers/android_device.py until
-#                    it is removed.
 import gzip
 import logging
 import os
+import shlex
 import subprocess
 import tempfile
 
-class AndroidDevice(object):
-    """This class controls the device via adb commands."""
 
-    def __init__(self, serial_number):
+class AndroidDevice(object):
+    """This class controls the device via adb or shell commands."""
+
+    def __init__(self, serial_number=None):
+        """Initialize the serial number.
+
+        A non-empty serial number indicates that this process runs on a host
+        and controls the devices via adb. An empty serial number indicates that
+        this process runs on an Android device and executes shell commands.
+
+        Args:
+            serial_number: A string or None.
+        """
         self._serial_number = serial_number
 
+    @property
+    def _adb_mode(self):
+        """Returns whether this objects executes adb commands."""
+        return bool(self._serial_number)
+
     def AdbPull(self, src, dst):
+        if not self._adb_mode:
+            raise NotImplementedError("Cannot execute `adb pull` on device.")
         cmd = ["adb", "-s", self._serial_number, "pull", src, dst]
         env = os.environ.copy()
         if "ADB_COMPRESSION" not in env:
             env["ADB_COMPRESSION"] = "0"
-        subprocess.check_call(cmd, shell=False, env=env, stdin=subprocess.PIPE,
-                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        subprocess.run(cmd, shell=False, env=env, check=True,
+                       stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+                       stderr=subprocess.PIPE)
 
     def Execute(self, *args):
         """Executes a command.
 
+        The caller should escape special characters.
+
         Args:
             args: Strings, the arguments.
 
@@ -48,9 +66,14 @@
             Stdout as a string, stderr as a string, and return code as an
             integer.
         """
-        cmd = ["adb", "-s", self._serial_number, "shell"]
-        cmd.extend(args)
-        proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
+        if self._adb_mode:
+            cmd = ["adb", "-s", self._serial_number, "shell"]
+            cmd.extend(args)
+        else:
+            cmd = " ".join(args)
+
+        proc = subprocess.Popen(cmd, shell=not self._adb_mode,
+                                stdin=subprocess.DEVNULL,
                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         out, err = proc.communicate()
         # Compatible with python2 and python3
@@ -192,6 +215,19 @@
         else:
             return 64
 
+    def GetLlndkList(self):
+        """Loads the list of LLNDK library names from the device.
+
+        Returns:
+            A list of strings, the library names including ".so".
+        """
+        out, err, return_code = self.Execute("cat",
+                                             "/system/etc/llndk.libraries.txt")
+        if err.strip() or return_code != 0:
+            raise IOError("`cat /system/etc/llndk.libraries.txt` "
+                          f"stdout: {out}\nstderr: {err}\n")
+        return out.split()
+
     def IsRoot(self):
         """Returns whether adb has root privilege on the device."""
         out, err, return_code = self.Execute("id")
@@ -245,8 +281,7 @@
         if '"' in name_pattern or "'" in name_pattern:
             raise ValueError("File name pattern contains quotes.")
         out, err, return_code = self.Execute("find", path, "-name",
-                                             "'" + name_pattern + "'",
-                                             *options)
+                                             f"'{name_pattern}'", *options)
         if return_code != 0 or err.strip():
             raise IOError("`find %s -name '%s' %s` stdout: %s\nstderr: %s" %
                           (path, name_pattern, " ".join(options), out, err))