Merge "Output informational messages only when BP2BUILD_VERBOSE is set."
diff --git a/apex/apex_test.go b/apex/apex_test.go
index dbe9180..6abd8ff 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -949,8 +949,10 @@
 	// mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	// ensureNotContains(t, mylib2Cflags, "-include ")
 
-	// Ensure that genstub is invoked with --apex
-	ensureContains(t, "--apex", ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"])
+	// Ensure that genstub for platform-provided lib is invoked with --systemapi
+	ensureContains(t, ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
+	// Ensure that genstub for apex-provided lib is invoked with --apex
+	ensureContains(t, ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_shared_12").Rule("genStubSrc").Args["flags"], "--apex")
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"lib64/mylib.so",
@@ -1134,8 +1136,8 @@
 	mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylib2Cflags, "-include ")
 
-	// Ensure that genstub is invoked with --apex
-	ensureContains(t, "--apex", ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("genStubSrc").Args["flags"])
+	// Ensure that genstub is invoked with --systemapi
+	ensureContains(t, ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("genStubSrc").Args["flags"], "--systemapi")
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"lib64/mylib.so",
diff --git a/cc/library.go b/cc/library.go
index c445a42..e2d5ef6 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1032,9 +1032,19 @@
 			ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile)
 			return Objects{}
 		}
+		// b/239274367 --apex and --systemapi filters symbols tagged with # apex and #
+		// systemapi, respectively. The former is for symbols defined in platform libraries
+		// and the latter is for symbols defined in APEXes.
+		var flag string
+		if ctx.Module().(android.ApexModule).NotInPlatform() {
+			flag = "--apex"
+		} else {
+			// TODO(b/239274367) drop --apex when #apex is replaced with #systemapi
+			// in the map.txt files of platform libraries
+			flag = "--systemapi --apex"
+		}
 		nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
-			android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion),
-			"--apex")
+			android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion), flag)
 		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		library.versionScriptPath = android.OptionalPathForPath(
 			nativeAbiResult.versionScript)
diff --git a/cc/ndk_api_coverage_parser/__init__.py b/cc/ndk_api_coverage_parser/__init__.py
index 8b9cd66..752f7d4 100755
--- a/cc/ndk_api_coverage_parser/__init__.py
+++ b/cc/ndk_api_coverage_parser/__init__.py
@@ -23,6 +23,7 @@
 from xml.etree.ElementTree import Element, SubElement, tostring
 from symbolfile import (
     ALL_ARCHITECTURES,
+    Filter,
     FUTURE_API_LEVEL,
     MultiplyDefinedSymbolError,
     SymbolFileParser,
@@ -139,9 +140,8 @@
 
     with open(args.symbol_file) as symbol_file:
         try:
-            versions = SymbolFileParser(
-                symbol_file, api_map, "", FUTURE_API_LEVEL, True, True
-            ).parse()
+            filt = Filter("", FUTURE_API_LEVEL, True, True, True)
+            versions = SymbolFileParser(symbol_file, api_map, filt).parse()
         except MultiplyDefinedSymbolError as ex:
             sys.exit('{}: error: {}'.format(args.symbol_file, ex))
 
diff --git a/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
index 141059c..7c6ef68 100644
--- a/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
+++ b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
@@ -20,7 +20,7 @@
 import unittest
 
 from xml.etree.ElementTree import fromstring
-from symbolfile import FUTURE_API_LEVEL, SymbolFileParser
+from symbolfile import Filter, FUTURE_API_LEVEL, SymbolFileParser
 import ndk_api_coverage_parser as nparser
 
 
@@ -78,9 +78,8 @@
         """
             )
         )
-        parser = SymbolFileParser(
-            input_file, {}, "", FUTURE_API_LEVEL, True, True
-        )
+        filt = Filter("", FUTURE_API_LEVEL, True, True, True)
+        parser = SymbolFileParser(input_file, {}, filt)
         generator = nparser.XmlGenerator(io.StringIO())
         result = generator.convertToXml(parser.parse())
         expected = fromstring(
diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py
index 5e6b8f5..f893d41 100755
--- a/cc/ndkstubgen/__init__.py
+++ b/cc/ndkstubgen/__init__.py
@@ -29,15 +29,12 @@
 class Generator:
     """Output generator that writes stub source files and version scripts."""
     def __init__(self, src_file: TextIO, version_script: TextIO,
-                 symbol_list: TextIO, arch: Arch, api: int, llndk: bool,
-                 apex: bool) -> None:
+                 symbol_list: TextIO, filt: symbolfile.Filter) -> None:
         self.src_file = src_file
         self.version_script = version_script
         self.symbol_list = symbol_list
-        self.arch = arch
-        self.api = api
-        self.llndk = llndk
-        self.apex = apex
+        self.filter = filt
+        self.api = filt.api
 
     def write(self, versions: Iterable[Version]) -> None:
         """Writes all symbol data to the output files."""
@@ -47,8 +44,7 @@
 
     def write_version(self, version: Version) -> None:
         """Writes a single version block's data to the output files."""
-        if symbolfile.should_omit_version(version, self.arch, self.api,
-                                          self.llndk, self.apex):
+        if self.filter.should_omit_version(version):
             return
 
         section_versioned = symbolfile.symbol_versioned_in_api(
@@ -56,8 +52,7 @@
         version_empty = True
         pruned_symbols = []
         for symbol in version.symbols:
-            if symbolfile.should_omit_symbol(symbol, self.arch, self.api,
-                                             self.llndk, self.apex):
+            if self.filter.should_omit_symbol(symbol):
                 continue
 
             if symbolfile.symbol_versioned_in_api(symbol.tags, self.api):
@@ -110,12 +105,12 @@
     parser.add_argument(
         '--apex',
         action='store_true',
-        help='Use the APEX variant. Note: equivalent to --system-api.')
+        help='Use the APEX variant.')
     parser.add_argument(
-        '--system-api',
+        '--systemapi',
         action='store_true',
-        dest='apex',
-        help='Use the SystemAPI variant. Note: equivalent to --apex.')
+        dest='systemapi',
+        help='Use the SystemAPI variant.')
 
     parser.add_argument('--api-map',
                         type=resolved_path,
@@ -152,11 +147,10 @@
         verbosity = 2
     logging.basicConfig(level=verbose_map[verbosity])
 
+    filt = symbolfile.Filter(args.arch, api, args.llndk, args.apex, args.systemapi)
     with args.symbol_file.open() as symbol_file:
         try:
-            versions = symbolfile.SymbolFileParser(symbol_file, api_map,
-                                                   args.arch, api, args.llndk,
-                                                   args.apex).parse()
+          versions = symbolfile.SymbolFileParser(symbol_file, api_map, filt).parse()
         except symbolfile.MultiplyDefinedSymbolError as ex:
             sys.exit(f'{args.symbol_file}: error: {ex}')
 
@@ -164,7 +158,7 @@
         with args.version_script.open('w') as version_script:
             with args.symbol_list.open('w') as symbol_list:
                 generator = Generator(src_file, version_script, symbol_list,
-                                      args.arch, api, args.llndk, args.apex)
+                                      filt)
                 generator.write(versions)
 
 
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index c8cd056..450719b 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -18,6 +18,7 @@
 import io
 import textwrap
 import unittest
+from copy import copy
 
 import symbolfile
 from symbolfile import Arch, Tags
@@ -29,6 +30,9 @@
 
 
 class GeneratorTest(unittest.TestCase):
+    def setUp(self) -> None:
+        self.filter = symbolfile.Filter(Arch('arm'), 9, False, False)
+
     def test_omit_version(self) -> None:
         # Thorough testing of the cases involved here is handled by
         # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
@@ -37,7 +41,7 @@
         symbol_list_file = io.StringIO()
         generator = ndkstubgen.Generator(src_file,
                                          version_file, symbol_list_file,
-                                         Arch('arm'), 9, False, False)
+                                         self.filter)
 
         version = symbolfile.Version('VERSION_PRIVATE', None, Tags(), [
             symbolfile.Symbol('foo', Tags()),
@@ -70,7 +74,7 @@
         symbol_list_file = io.StringIO()
         generator = ndkstubgen.Generator(src_file,
                                          version_file, symbol_list_file,
-                                         Arch('arm'), 9, False, False)
+                                         self.filter)
 
         version = symbolfile.Version('VERSION_1', None, Tags(), [
             symbolfile.Symbol('foo', Tags.from_strs(['x86'])),
@@ -106,7 +110,7 @@
         symbol_list_file = io.StringIO()
         generator = ndkstubgen.Generator(src_file,
                                          version_file, symbol_list_file,
-                                         Arch('arm'), 9, False, False)
+                                         self.filter)
 
         versions = [
             symbolfile.Version('VERSION_1', None, Tags(), [
@@ -162,6 +166,9 @@
 
 
 class IntegrationTest(unittest.TestCase):
+    def setUp(self) -> None:
+        self.filter = symbolfile.Filter(Arch('arm'), 9, False, False)
+
     def test_integration(self) -> None:
         api_map = {
             'O': 9000,
@@ -199,8 +206,7 @@
                 wobble;
             } VERSION_4;
         """))
-        parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
-                                             9, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, api_map, self.filter)
         versions = parser.parse()
 
         src_file = io.StringIO()
@@ -208,7 +214,7 @@
         symbol_list_file = io.StringIO()
         generator = ndkstubgen.Generator(src_file,
                                          version_file, symbol_list_file,
-                                         Arch('arm'), 9, False, False)
+                                         self.filter)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -263,16 +269,18 @@
                     *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
-                                             9001, False, False)
+        f = copy(self.filter)
+        f.api = 9001
+        parser = symbolfile.SymbolFileParser(input_file, api_map, f)
         versions = parser.parse()
 
         src_file = io.StringIO()
         version_file = io.StringIO()
         symbol_list_file = io.StringIO()
+        f = copy(self.filter)
+        f.api = 9001
         generator = ndkstubgen.Generator(src_file,
-                                         version_file, symbol_list_file,
-                                         Arch('arm'), 9001, False, False)
+                                         version_file, symbol_list_file, f)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -322,8 +330,9 @@
             } VERSION_2;
 
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        f = copy(self.filter)
+        f.api = 16
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
 
         with self.assertRaises(
                 symbolfile.MultiplyDefinedSymbolError) as ex_context:
@@ -370,16 +379,18 @@
                 wobble;
             } VERSION_4;
         """))
-        parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
-                                             9, False, True)
+        f = copy(self.filter)
+        f.apex = True
+        parser = symbolfile.SymbolFileParser(input_file, api_map, f)
         versions = parser.parse()
 
         src_file = io.StringIO()
         version_file = io.StringIO()
         symbol_list_file = io.StringIO()
+        f = copy(self.filter)
+        f.apex = True
         generator = ndkstubgen.Generator(src_file,
-                                         version_file, symbol_list_file,
-                                         Arch('arm'), 9, False, True)
+                                         version_file, symbol_list_file, f)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -428,20 +439,19 @@
                     *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'),
-                                             9, llndk=False, apex=True)
+        f = copy(self.filter)
+        f.apex = True
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
         versions = parser.parse()
 
         src_file = io.StringIO()
         version_file = io.StringIO()
         symbol_list_file = io.StringIO()
+        f = copy(self.filter)
+        f.apex = True
         generator = ndkstubgen.Generator(src_file,
                                          version_file,
-                                         symbol_list_file,
-                                         Arch('arm'),
-                                         9,
-                                         llndk=False,
-                                         apex=True)
+                                         symbol_list_file, f)
         generator.write(versions)
 
         self.assertEqual('', src_file.getvalue())
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index f8d1841..471a12f 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -78,12 +78,17 @@
     @property
     def has_mode_tags(self) -> bool:
         """Returns True if any mode tags (apex, llndk, etc) are set."""
-        return self.has_apex_tags or self.has_llndk_tags
+        return self.has_apex_tags or self.has_llndk_tags or self.has_systemapi_tags
 
     @property
     def has_apex_tags(self) -> bool:
         """Returns True if any APEX tags are set."""
-        return 'apex' in self.tags or 'systemapi' in self.tags
+        return 'apex' in self.tags
+
+    @property
+    def has_systemapi_tags(self) -> bool:
+        """Returns True if any APEX tags are set."""
+        return 'systemapi' in self.tags
 
     @property
     def has_llndk_tags(self) -> bool:
@@ -198,50 +203,57 @@
     """
     return split_tag(tag)[1]
 
-
-def _should_omit_tags(tags: Tags, arch: Arch, api: int, llndk: bool,
-                      apex: bool) -> bool:
-    """Returns True if the tagged object should be omitted.
-
-    This defines the rules shared between version tagging and symbol tagging.
+class Filter:
+    """A filter encapsulates a condition that tells whether a version or a
+    symbol should be omitted or not
     """
-    # The apex and llndk tags will only exclude APIs from other modes. If in
-    # APEX or LLNDK mode and neither tag is provided, we fall back to the
-    # default behavior because all NDK symbols are implicitly available to APEX
-    # and LLNDK.
-    if tags.has_mode_tags:
-        if not apex and not llndk:
+
+    def __init__(self, arch: Arch, api: int, llndk: bool = False, apex: bool = False, systemapi: bool = False):
+        self.arch = arch
+        self.api = api
+        self.llndk = llndk
+        self.apex = apex
+        self.systemapi = systemapi
+
+    def _should_omit_tags(self, tags: Tags) -> bool:
+        """Returns True if the tagged object should be omitted.
+
+        This defines the rules shared between version tagging and symbol tagging.
+        """
+        # The apex and llndk tags will only exclude APIs from other modes. If in
+        # APEX or LLNDK mode and neither tag is provided, we fall back to the
+        # default behavior because all NDK symbols are implicitly available to
+        # APEX and LLNDK.
+        if tags.has_mode_tags:
+            if self.apex and tags.has_apex_tags:
+                return False
+            if self.llndk and tags.has_llndk_tags:
+                return False
+            if self.systemapi and tags.has_systemapi_tags:
+                return False
             return True
-        if apex and not tags.has_apex_tags:
+        if not symbol_in_arch(tags, self.arch):
             return True
-        if llndk and not tags.has_llndk_tags:
+        if not symbol_in_api(tags, self.arch, self.api):
             return True
-    if not symbol_in_arch(tags, arch):
-        return True
-    if not symbol_in_api(tags, arch, api):
-        return True
-    return False
+        return False
 
+    def should_omit_version(self, version: Version) -> bool:
+        """Returns True if the version section should be omitted.
 
-def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool,
-                        apex: bool) -> bool:
-    """Returns True if the version section should be omitted.
+        We want to omit any sections that do not have any symbols we'll have in
+        the stub library. Sections that contain entirely future symbols or only
+        symbols for certain architectures.
+        """
+        if version.is_private:
+            return True
+        if version.tags.has_platform_only_tags:
+            return True
+        return self._should_omit_tags(version.tags)
 
-    We want to omit any sections that do not have any symbols we'll have in the
-    stub library. Sections that contain entirely future symbols or only symbols
-    for certain architectures.
-    """
-    if version.is_private:
-        return True
-    if version.tags.has_platform_only_tags:
-        return True
-    return _should_omit_tags(version.tags, arch, api, llndk, apex)
-
-
-def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool,
-                       apex: bool) -> bool:
-    """Returns True if the symbol should be omitted."""
-    return _should_omit_tags(symbol.tags, arch, api, llndk, apex)
+    def should_omit_symbol(self, symbol: Symbol) -> bool:
+        """Returns True if the symbol should be omitted."""
+        return self._should_omit_tags(symbol.tags)
 
 
 def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
@@ -316,14 +328,10 @@
 
 class SymbolFileParser:
     """Parses NDK symbol files."""
-    def __init__(self, input_file: TextIO, api_map: ApiMap, arch: Arch,
-                 api: int, llndk: bool, apex: bool) -> None:
+    def __init__(self, input_file: TextIO, api_map: ApiMap, filt: Filter) -> None:
         self.input_file = input_file
         self.api_map = api_map
-        self.arch = arch
-        self.api = api
-        self.llndk = llndk
-        self.apex = apex
+        self.filter = filt
         self.current_line: Optional[str] = None
 
     def parse(self) -> List[Version]:
@@ -352,13 +360,11 @@
         symbol_names = set()
         multiply_defined_symbols = set()
         for version in versions:
-            if should_omit_version(version, self.arch, self.api, self.llndk,
-                                   self.apex):
+            if self.filter.should_omit_version(version):
                 continue
 
             for symbol in version.symbols:
-                if should_omit_symbol(symbol, self.arch, self.api, self.llndk,
-                                      self.apex):
+                if self.filter.should_omit_symbol(symbol):
                     continue
 
                 if symbol.name in symbol_names:
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index c1e8219..e17a8d0 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -19,7 +19,8 @@
 import unittest
 
 import symbolfile
-from symbolfile import Arch, Tag, Tags, Version
+from symbolfile import Arch, Tag, Tags, Version, Symbol, Filter
+from copy import copy
 
 # pylint: disable=missing-docstring
 
@@ -202,178 +203,188 @@
 
 
 class OmitVersionTest(unittest.TestCase):
+    def setUp(self) -> None:
+        self.filter = Filter(arch = Arch('arm'), api = 9)
+        self.version = Version('foo', None, Tags(), [])
+
+    def assertOmit(self, f: Filter, v: Version) -> None:
+        self.assertTrue(f.should_omit_version(v))
+
+    def assertInclude(self, f: Filter, v: Version) -> None:
+        self.assertFalse(f.should_omit_version(v))
+
     def test_omit_private(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
-                False, False))
+        f = self.filter
+        v = self.version
 
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo_PRIVATE', None, Tags(), []),
-                Arch('arm'), 9, False, False))
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo_PLATFORM', None, Tags(), []),
-                Arch('arm'), 9, False, False))
+        self.assertInclude(f, v)
 
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None,
-                                   Tags.from_strs(['platform-only']), []),
-                Arch('arm'), 9, False, False))
+        v.name = 'foo_PRIVATE'
+        self.assertOmit(f, v)
+
+        v.name = 'foo_PLATFORM'
+        self.assertOmit(f, v)
+
+        v.name = 'foo'
+        v.tags = Tags.from_strs(['platform-only'])
+        self.assertOmit(f, v)
 
     def test_omit_llndk(self) -> None:
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []),
-                Arch('arm'), 9, False, False))
+        f = self.filter
+        v = self.version
+        v_llndk = copy(v)
+        v_llndk.tags = Tags.from_strs(['llndk'])
 
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
-                True, False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []),
-                Arch('arm'), 9, True, False))
+        self.assertOmit(f, v_llndk)
+
+        f.llndk = True
+        self.assertInclude(f, v)
+        self.assertInclude(f, v_llndk)
 
     def test_omit_apex(self) -> None:
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['apex']), []),
-                Arch('arm'), 9, False, False))
+        f = self.filter
+        v = self.version
+        v_apex = copy(v)
+        v_apex.tags = Tags.from_strs(['apex'])
+        v_systemapi = copy(v)
+        v_systemapi.tags = Tags.from_strs(['systemapi'])
 
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
-                False, True))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['apex']), []),
-                Arch('arm'), 9, False, True))
+        self.assertOmit(f, v_apex)
+
+        f.apex = True
+        self.assertInclude(f, v)
+        self.assertInclude(f, v_apex)
+        self.assertOmit(f, v_systemapi)
 
     def test_omit_systemapi(self) -> None:
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['systemapi']),
-                                   []), Arch('arm'), 9, False, False))
+        f = self.filter
+        v = self.version
+        v_apex = copy(v)
+        v_apex.tags = Tags.from_strs(['apex'])
+        v_systemapi = copy(v)
+        v_systemapi.tags = Tags.from_strs(['systemapi'])
 
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
-                False, True))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['systemapi']),
-                                   []), Arch('arm'), 9, False, True))
+        self.assertOmit(f, v_systemapi)
+
+        f.systemapi = True
+        self.assertInclude(f, v)
+        self.assertInclude(f, v_systemapi)
+        self.assertOmit(f, v_apex)
 
     def test_omit_arch(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
-                False, False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['arm']), []),
-                Arch('arm'), 9, False, False))
+        f_arm = self.filter
+        v_none = self.version
+        self.assertInclude(f_arm, v_none)
 
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags.from_strs(['x86']), []),
-                Arch('arm'), 9, False, False))
+        v_arm = copy(v_none)
+        v_arm.tags = Tags.from_strs(['arm'])
+        self.assertInclude(f_arm, v_arm)
+
+        v_x86 = copy(v_none)
+        v_x86.tags = Tags.from_strs(['x86'])
+        self.assertOmit(f_arm, v_x86)
 
     def test_omit_api(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
-                False, False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None,
-                                   Tags.from_strs(['introduced=9']), []),
-                Arch('arm'), 9, False, False))
+        f_api9 = self.filter
+        v_none = self.version
+        self.assertInclude(f_api9, v_none)
 
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None,
-                                   Tags.from_strs(['introduced=14']), []),
-                Arch('arm'), 9, False, False))
+        v_api9 = copy(v_none)
+        v_api9.tags = Tags.from_strs(['introduced=9'])
+        self.assertInclude(f_api9, v_api9)
+
+        v_api14 = copy(v_none)
+        v_api14.tags = Tags.from_strs(['introduced=14'])
+        self.assertOmit(f_api9, v_api14)
 
 
 class OmitSymbolTest(unittest.TestCase):
-    def test_omit_llndk(self) -> None:
-        self.assertTrue(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
-                Arch('arm'), 9, False, False))
+    def setUp(self) -> None:
+        self.filter = Filter(arch = Arch('arm'), api = 9)
 
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
-                                          Arch('arm'), 9, True, False))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
-                Arch('arm'), 9, True, False))
+    def assertOmit(self, f: Filter, s: Symbol) -> None:
+        self.assertTrue(f.should_omit_symbol(s))
+
+    def assertInclude(self, f: Filter, s: Symbol) -> None:
+        self.assertFalse(f.should_omit_symbol(s))
+
+    def test_omit_llndk(self) -> None:
+        f_none = self.filter
+        f_llndk = copy(f_none)
+        f_llndk.llndk = True
+
+        s_none = Symbol('foo', Tags())
+        s_llndk = Symbol('foo', Tags.from_strs(['llndk']))
+
+        self.assertOmit(f_none, s_llndk)
+        self.assertInclude(f_llndk, s_none)
+        self.assertInclude(f_llndk, s_llndk)
 
     def test_omit_apex(self) -> None:
-        self.assertTrue(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
-                Arch('arm'), 9, False, False))
+        f_none = self.filter
+        f_apex = copy(f_none)
+        f_apex.apex = True
 
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
-                                          Arch('arm'), 9, False, True))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
-                Arch('arm'), 9, False, True))
+        s_none = Symbol('foo', Tags())
+        s_apex = Symbol('foo', Tags.from_strs(['apex']))
+        s_systemapi = Symbol('foo', Tags.from_strs(['systemapi']))
+
+        self.assertOmit(f_none, s_apex)
+        self.assertInclude(f_apex, s_none)
+        self.assertInclude(f_apex, s_apex)
+        self.assertOmit(f_apex, s_systemapi)
 
     def test_omit_systemapi(self) -> None:
-        self.assertTrue(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])),
-                Arch('arm'), 9, False, False))
+        f_none = self.filter
+        f_systemapi = copy(f_none)
+        f_systemapi.systemapi = True
 
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
-                                          Arch('arm'), 9, False, True))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])),
-                Arch('arm'), 9, False, True))
+        s_none = Symbol('foo', Tags())
+        s_apex = Symbol('foo', Tags.from_strs(['apex']))
+        s_systemapi = Symbol('foo', Tags.from_strs(['systemapi']))
+
+        self.assertOmit(f_none, s_systemapi)
+        self.assertInclude(f_systemapi, s_none)
+        self.assertInclude(f_systemapi, s_systemapi)
+        self.assertOmit(f_systemapi, s_apex)
+
+    def test_omit_apex_and_systemapi(self) -> None:
+        f = self.filter
+        f.systemapi = True
+        f.apex = True
+
+        s_none = Symbol('foo', Tags())
+        s_apex = Symbol('foo', Tags.from_strs(['apex']))
+        s_systemapi = Symbol('foo', Tags.from_strs(['systemapi']))
+        self.assertInclude(f, s_none)
+        self.assertInclude(f, s_apex)
+        self.assertInclude(f, s_systemapi)
 
     def test_omit_arch(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
-                                          Arch('arm'), 9, False, False))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['arm'])), Arch('arm'),
-                9, False, False))
+        f_arm = self.filter
+        s_none = Symbol('foo', Tags())
+        s_arm = Symbol('foo', Tags.from_strs(['arm']))
+        s_x86 = Symbol('foo', Tags.from_strs(['x86']))
 
-        self.assertTrue(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['x86'])), Arch('arm'),
-                9, False, False))
+        self.assertInclude(f_arm, s_none)
+        self.assertInclude(f_arm, s_arm)
+        self.assertOmit(f_arm, s_x86)
 
     def test_omit_api(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
-                                          Arch('arm'), 9, False, False))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['introduced=9'])),
-                Arch('arm'), 9, False, False))
+        f_api9 = self.filter
+        s_none = Symbol('foo', Tags())
+        s_api9 = Symbol('foo', Tags.from_strs(['introduced=9']))
+        s_api14 = Symbol('foo', Tags.from_strs(['introduced=14']))
 
-        self.assertTrue(
-            symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])),
-                Arch('arm'), 9, False, False))
+        self.assertInclude(f_api9, s_none)
+        self.assertInclude(f_api9, s_api9)
+        self.assertOmit(f_api9, s_api14)
 
 
 class SymbolFileParseTest(unittest.TestCase):
+    def setUp(self) -> None:
+        self.filter = Filter(arch = Arch('arm'), api = 16)
+
     def test_next_line(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             foo
@@ -382,8 +393,7 @@
             # baz
             qux
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         self.assertIsNone(parser.current_line)
 
         self.assertEqual('foo', parser.next_line().strip())
@@ -409,8 +419,7 @@
             VERSION_2 {
             } VERSION_1; # asdf
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
 
         parser.next_line()
         version = parser.parse_version()
@@ -419,8 +428,8 @@
         self.assertEqual(Tags.from_strs(['foo', 'bar']), version.tags)
 
         expected_symbols = [
-            symbolfile.Symbol('baz', Tags()),
-            symbolfile.Symbol('qux', Tags.from_strs(['woodly', 'doodly'])),
+            Symbol('baz', Tags()),
+            Symbol('qux', Tags.from_strs(['woodly', 'doodly'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -434,8 +443,7 @@
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
@@ -446,8 +454,7 @@
                 foo:
             }
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
@@ -457,8 +464,7 @@
             foo;
             bar; # baz qux
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
 
         parser.next_line()
         symbol = parser.parse_symbol()
@@ -476,8 +482,7 @@
                 *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
@@ -489,8 +494,7 @@
                     *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         parser.next_line()
         version = parser.parse_version()
         self.assertEqual([], version.symbols)
@@ -501,8 +505,7 @@
                 foo
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
@@ -510,8 +513,7 @@
     def test_parse_fails_invalid_input(self) -> None:
         with self.assertRaises(symbolfile.ParseError):
             input_file = io.StringIO('foo')
-            parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'),
-                                                 16, False, False)
+            parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
             parser.parse()
 
     def test_parse(self) -> None:
@@ -532,19 +534,18 @@
                     qwerty;
             } VERSION_1;
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, self.filter)
         versions = parser.parse()
 
         expected = [
             symbolfile.Version('VERSION_1', None, Tags(), [
-                symbolfile.Symbol('foo', Tags()),
-                symbolfile.Symbol('bar', Tags.from_strs(['baz'])),
+                Symbol('foo', Tags()),
+                Symbol('bar', Tags.from_strs(['baz'])),
             ]),
             symbolfile.Version(
                 'VERSION_2', 'VERSION_1', Tags.from_strs(['wasd']), [
-                    symbolfile.Symbol('woodly', Tags()),
-                    symbolfile.Symbol('doodly', Tags.from_strs(['asdf'])),
+                    Symbol('woodly', Tags()),
+                    Symbol('doodly', Tags.from_strs(['asdf'])),
                 ]),
         ]
 
@@ -559,8 +560,9 @@
                 qux; # apex
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
-                                             False, True)
+        f = copy(self.filter)
+        f.llndk = True
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
 
         parser.next_line()
         version = parser.parse_version()
@@ -568,10 +570,10 @@
         self.assertIsNone(version.base)
 
         expected_symbols = [
-            symbolfile.Symbol('foo', Tags()),
-            symbolfile.Symbol('bar', Tags.from_strs(['llndk'])),
-            symbolfile.Symbol('baz', Tags.from_strs(['llndk', 'apex'])),
-            symbolfile.Symbol('qux', Tags.from_strs(['apex'])),
+            Symbol('foo', Tags()),
+            Symbol('bar', Tags.from_strs(['llndk'])),
+            Symbol('baz', Tags.from_strs(['llndk', 'apex'])),
+            Symbol('qux', Tags.from_strs(['apex'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 57c171f..7ab5285 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -1339,9 +1339,9 @@
 .intermediates/myjavalib.stubs.source.module_lib/android_common/metalava/myjavalib.stubs.source.module_lib_removed.txt -> sdk_library/module-lib/myjavalib-removed.txt
 `),
 		checkMergeZips(
+			".intermediates/mysdk/common_os/tmp/sdk_library/module-lib/myjavalib_stub_sources.zip",
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
 			".intermediates/mysdk/common_os/tmp/sdk_library/system/myjavalib_stub_sources.zip",
-			".intermediates/mysdk/common_os/tmp/sdk_library/module-lib/myjavalib_stub_sources.zip",
 		),
 	)
 }
diff --git a/sdk/update.go b/sdk/update.go
index 995da74..c555ddc 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -108,7 +108,7 @@
 
 	mergeZips = pctx.AndroidStaticRule("SnapshotMergeZips",
 		blueprint.RuleParams{
-			Command: `${config.MergeZipsCmd} $out $in`,
+			Command: `${config.MergeZipsCmd} -s $out $in`,
 			CommandDeps: []string{
 				"${config.MergeZipsCmd}",
 			},
@@ -481,7 +481,7 @@
 	// Copy the build number file into the snapshot.
 	builder.CopyToSnapshot(ctx.Config().BuildNumberFile(ctx), BUILD_NUMBER_FILE)
 
-	filesToZip := builder.filesToZip
+	filesToZip := android.SortedUniquePaths(builder.filesToZip)
 
 	// zip them all
 	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotFileSuffix)
@@ -517,7 +517,7 @@
 			Description: outputDesc,
 			Rule:        mergeZips,
 			Input:       zipFile,
-			Inputs:      builder.zipsToMerge,
+			Inputs:      android.SortedUniquePaths(builder.zipsToMerge),
 			Output:      outputZipFile,
 		})
 	}