Merge "Revert^2 "Support shared libraries in CreateContextFromClassLoader.""
diff --git a/Android.mk b/Android.mk
index 3388b35..ba131c6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -474,7 +474,7 @@
 define build-art-hiddenapi
 $(shell if [ ! -d frameworks/base ]; then \
   mkdir -p ${TARGET_OUT_COMMON_INTERMEDIATES}/PACKAGING; \
-	touch ${TARGET_OUT_COMMON_INTERMEDIATES}/PACKAGING/hiddenapi-{whitelist,blacklist,dark-greylist,light-greylist}.txt; \
+	touch ${TARGET_OUT_COMMON_INTERMEDIATES}/PACKAGING/hiddenapi-flags.csv; \
   fi;)
 endef
 
diff --git a/cmdline/cmdline.h b/cmdline/cmdline.h
index 95ab123..81a2179 100644
--- a/cmdline/cmdline.h
+++ b/cmdline/cmdline.h
@@ -85,7 +85,9 @@
   }
 }
 
-static Runtime* StartRuntime(const char* boot_image_location, InstructionSet instruction_set) {
+static Runtime* StartRuntime(const char* boot_image_location,
+                             InstructionSet instruction_set,
+                             const std::vector<const char*>& runtime_args) {
   CHECK(boot_image_location != nullptr);
 
   RuntimeOptions options;
@@ -101,13 +103,19 @@
     std::string boot_image_option;
     boot_image_option += "-Ximage:";
     boot_image_option += boot_image_location;
-    options.push_back(std::make_pair(boot_image_option.c_str(), nullptr));
+    options.push_back(std::make_pair(boot_image_option, nullptr));
   }
 
   // Instruction set.
   options.push_back(
       std::make_pair("imageinstructionset",
                      reinterpret_cast<const void*>(GetInstructionSetString(instruction_set))));
+
+  // Explicit runtime args.
+  for (const char* runtime_arg : runtime_args) {
+    options.push_back(std::make_pair(runtime_arg, nullptr));
+  }
+
   // None of the command line tools need sig chain. If this changes we'll need
   // to upgrade this option to a proper parameter.
   options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
@@ -154,6 +162,14 @@
           PrintUsage();
           return false;
         }
+      } else if (option == "--runtime-arg") {
+        if (i + 1 == argc) {
+          fprintf(stderr, "Missing argument for --runtime-arg\n");
+          PrintUsage();
+          return false;
+        }
+        ++i;
+        runtime_args_.push_back(argv[i]);
       } else if (option.starts_with("--output=")) {
         output_name_ = option.substr(strlen("--output=")).ToString();
         const char* filename = output_name_.c_str();
@@ -209,6 +225,12 @@
         "      Default: %s\n"
         "\n",
         GetInstructionSetString(kRuntimeISA));
+    usage +=
+        "  --runtime-arg <argument> used to specify various arguments for the runtime\n"
+        "      such as initial heap size, maximum heap size, and verbose output.\n"
+        "      Use a separate --runtime-arg switch for each argument.\n"
+        "      Example: --runtime-arg -Xms256m\n"
+        "\n";
     usage +=  // Optional.
         "  --output=<file> may be used to send the output to a file.\n"
         "      Example: --output=/tmp/oatdump.txt\n"
@@ -221,6 +243,8 @@
   const char* boot_image_location_ = nullptr;
   // Specified by --instruction-set.
   InstructionSet instruction_set_ = InstructionSet::kNone;
+  // Runtime arguments specified by --runtime-arg.
+  std::vector<const char*> runtime_args_;
   // Specified by --output.
   std::ostream* os_ = &std::cout;
   std::unique_ptr<std::ofstream> out_;  // If something besides cout is used
@@ -383,7 +407,7 @@
   Runtime* CreateRuntime(CmdlineArgs* args) {
     CHECK(args != nullptr);
 
-    return StartRuntime(args->boot_image_location_, args->instruction_set_);
+    return StartRuntime(args->boot_image_location_, args->instruction_set_, args_->runtime_args_);
   }
 };
 }  // namespace art
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index bef5be1..b28c7e0 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -71,6 +71,7 @@
       count_hotness_in_compiled_code_(false),
       resolve_startup_const_strings_(false),
       check_profiled_methods_(ProfileMethodsCheck::kNone),
+      max_image_block_size_(std::numeric_limits<uint32_t>::max()),
       register_allocation_strategy_(RegisterAllocator::kRegisterAllocatorDefault),
       passes_to_run_(nullptr) {
 }
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index f0970a9..17a779c 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -335,6 +335,14 @@
     return check_profiled_methods_;
   }
 
+  uint32_t MaxImageBlockSize() const {
+    return max_image_block_size_;
+  }
+
+  void SetMaxImageBlockSize(uint32_t size) {
+    max_image_block_size_ = size;
+  }
+
  private:
   bool ParseDumpInitFailures(const std::string& option, std::string* error_msg);
   void ParseDumpCfgPasses(const StringPiece& option, UsageFn Usage);
@@ -424,6 +432,9 @@
   // up compiled and are not punted.
   ProfileMethodsCheck check_profiled_methods_;
 
+  // Maximum solid block size in the generated image.
+  uint32_t max_image_block_size_;
+
   RegisterAllocator::Strategy register_allocation_strategy_;
 
   // If not null, specifies optimization passes which will be run instead of defaults.
diff --git a/compiler/driver/compiler_options_map-inl.h b/compiler/driver/compiler_options_map-inl.h
index c7334a7..7e2a64b 100644
--- a/compiler/driver/compiler_options_map-inl.h
+++ b/compiler/driver/compiler_options_map-inl.h
@@ -84,6 +84,7 @@
   if (map.Exists(Base::CheckProfiledMethods)) {
     options->check_profiled_methods_ = *map.Get(Base::CheckProfiledMethods);
   }
+  map.AssignIfExists(Base::MaxImageBlockSize, &options->max_image_block_size_);
 
   if (map.Exists(Base::DumpTimings)) {
     options->dump_timings_ = true;
@@ -201,7 +202,11 @@
 
       .Define("--verbose-methods=_")
           .template WithType<ParseStringList<','>>()
-          .IntoKey(Map::VerboseMethods);
+          .IntoKey(Map::VerboseMethods)
+
+      .Define("--max-image-block-size=_")
+          .template WithType<unsigned int>()
+          .IntoKey(Map::MaxImageBlockSize);
 }
 
 #pragma GCC diagnostic pop
diff --git a/compiler/driver/compiler_options_map.def b/compiler/driver/compiler_options_map.def
index c2fac5e..0a9c873 100644
--- a/compiler/driver/compiler_options_map.def
+++ b/compiler/driver/compiler_options_map.def
@@ -65,5 +65,6 @@
 COMPILER_OPTIONS_KEY (Unit,                        DumpTimings)
 COMPILER_OPTIONS_KEY (Unit,                        DumpPassTimings)
 COMPILER_OPTIONS_KEY (Unit,                        DumpStats)
+COMPILER_OPTIONS_KEY (unsigned int,                MaxImageBlockSize)
 
 #undef COMPILER_OPTIONS_KEY
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 8f56f3e..edd6189 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -482,6 +482,8 @@
   UsageError("  --resolve-startup-const-strings=true|false: If true, the compiler eagerly");
   UsageError("      resolves strings referenced from const-string of startup methods.");
   UsageError("");
+  UsageError("  --max-image-block-size=<size>: Maximum solid block size for compressed images.");
+  UsageError("");
   UsageError("      Example: --compilation-reason=install");
   UsageError("");
   std::cerr << "See log for usage error information\n";
diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc
index 64b98cd..ebd829b 100644
--- a/dex2oat/linker/image_test.cc
+++ b/dex2oat/linker/image_test.cc
@@ -32,7 +32,11 @@
   // Compile multi-image with ImageLayoutA being the last image.
   {
     CompilationHelper helper;
-    Compile(ImageHeader::kStorageModeUncompressed, helper, "ImageLayoutA", {"LMyClass;"});
+    Compile(ImageHeader::kStorageModeUncompressed,
+            /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
+            helper,
+            "ImageLayoutA",
+            {"LMyClass;"});
     image_sizes = helper.GetImageObjectSectionSizes();
   }
   TearDown();
@@ -41,7 +45,11 @@
   // Compile multi-image with ImageLayoutB being the last image.
   {
     CompilationHelper helper;
-    Compile(ImageHeader::kStorageModeUncompressed, helper, "ImageLayoutB", {"LMyClass;"});
+    Compile(ImageHeader::kStorageModeUncompressed,
+            /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
+            helper,
+            "ImageLayoutB",
+            {"LMyClass;"});
     image_sizes_extra = helper.GetImageObjectSectionSizes();
   }
   // Make sure that the new stuff in the clinit in ImageLayoutB is in the last image and not in the
@@ -76,9 +84,8 @@
                              oat_file_end,
                              /*boot_image_begin=*/ 0u,
                              /*boot_image_size=*/ 0u,
-                             sizeof(void*),
-                             ImageHeader::kDefaultStorageMode,
-                             /*data_size=*/ 0u);
+                             sizeof(void*));
+
     ASSERT_TRUE(image_header.IsValid());
     ASSERT_TRUE(!image_header.IsAppImage());
 
@@ -97,6 +104,7 @@
 TEST_F(ImageTest, TestDefaultMethods) {
   CompilationHelper helper;
   Compile(ImageHeader::kStorageModeUncompressed,
+          /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
           helper,
           "DefaultMethods",
           {"LIface;", "LImpl;", "LIterableBase;"});
@@ -156,6 +164,7 @@
 TEST_F(ImageTest, TestSoftVerificationFailureDuringClassInitialization) {
   CompilationHelper helper;
   Compile(ImageHeader::kStorageModeUncompressed,
+          /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
           helper,
           "VerifySoftFailDuringClinit",
           /*image_classes=*/ {"LClassToInitialize;"},
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index cde6573..844a728 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -77,9 +77,10 @@
     CommonCompilerTest::SetUp();
   }
 
-  void TestWriteRead(ImageHeader::StorageMode storage_mode);
+  void TestWriteRead(ImageHeader::StorageMode storage_mode, uint32_t max_image_block_size);
 
   void Compile(ImageHeader::StorageMode storage_mode,
+               uint32_t max_image_block_size,
                /*out*/ CompilationHelper& out_helper,
                const std::string& extra_dex = "",
                const std::initializer_list<std::string>& image_classes = {},
@@ -362,6 +363,7 @@
 
 inline void ImageTest::Compile(
     ImageHeader::StorageMode storage_mode,
+    uint32_t max_image_block_size,
     CompilationHelper& helper,
     const std::string& extra_dex,
     const std::initializer_list<std::string>& image_classes,
@@ -376,6 +378,7 @@
   CreateCompilerDriver();
   // Set inline filter values.
   compiler_options_->SetInlineMaxCodeUnits(CompilerOptions::kDefaultInlineMaxCodeUnits);
+  compiler_options_->SetMaxImageBlockSize(max_image_block_size);
   image_classes_.clear();
   if (!extra_dex.empty()) {
     helper.extra_dex_files = OpenTestDexFiles(extra_dex.c_str());
@@ -399,9 +402,10 @@
   }
 }
 
-inline void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) {
+inline void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode,
+                                     uint32_t max_image_block_size) {
   CompilationHelper helper;
-  Compile(storage_mode, /*out*/ helper);
+  Compile(storage_mode, max_image_block_size, /*out*/ helper);
   std::vector<uint64_t> image_file_sizes;
   for (ScratchFile& image_file : helper.image_files) {
     std::unique_ptr<File> file(OS::OpenFileForReading(image_file.GetFilename().c_str()));
@@ -476,6 +480,10 @@
     } else if (image_file_size > 16 * KB) {
       // Compressed, file should be smaller than image. Not really valid for small images.
       ASSERT_LE(image_file_size, image_space->GetImageHeader().GetImageSize());
+      // TODO: Actually validate the blocks, this is hard since the blocks are not copied over for
+      // compressed images. Add kPageSize since image_size is rounded up to this.
+      ASSERT_GT(image_space->GetImageHeader().GetBlockCount() * max_image_block_size,
+                image_space->GetImageHeader().GetImageSize() - kPageSize);
     }
 
     image_space->VerifyImageAllocations();
diff --git a/dex2oat/linker/image_write_read_test.cc b/dex2oat/linker/image_write_read_test.cc
index 30996b5..5ddbd09 100644
--- a/dex2oat/linker/image_write_read_test.cc
+++ b/dex2oat/linker/image_write_read_test.cc
@@ -20,15 +20,23 @@
 namespace linker {
 
 TEST_F(ImageTest, WriteReadUncompressed) {
-  TestWriteRead(ImageHeader::kStorageModeUncompressed);
+  TestWriteRead(ImageHeader::kStorageModeUncompressed,
+                /*max_image_block_size=*/std::numeric_limits<uint32_t>::max());
 }
 
 TEST_F(ImageTest, WriteReadLZ4) {
-  TestWriteRead(ImageHeader::kStorageModeLZ4);
+  TestWriteRead(ImageHeader::kStorageModeLZ4,
+                /*max_image_block_size=*/std::numeric_limits<uint32_t>::max());
 }
 
 TEST_F(ImageTest, WriteReadLZ4HC) {
-  TestWriteRead(ImageHeader::kStorageModeLZ4HC);
+  TestWriteRead(ImageHeader::kStorageModeLZ4HC,
+                /*max_image_block_size=*/std::numeric_limits<uint32_t>::max());
+}
+
+
+TEST_F(ImageTest, WriteReadLZ4HCKBBlock) {
+  TestWriteRead(ImageHeader::kStorageModeLZ4HC, /*max_image_block_size=*/KB);
 }
 
 }  // namespace linker
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index a7c7fbf..231ac04 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -745,32 +745,93 @@
 
     // Image data size excludes the bitmap and the header.
     ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin());
-    ArrayRef<const uint8_t> raw_image_data(image_info.image_.Begin() + sizeof(ImageHeader),
-                                           image_header->GetImageSize() - sizeof(ImageHeader));
 
-    CHECK_EQ(image_header->storage_mode_, image_storage_mode_);
-    std::vector<uint8_t> compressed_data;
-    ArrayRef<const uint8_t> image_data =
-        MaybeCompressData(raw_image_data, image_storage_mode_, &compressed_data);
-    image_header->data_size_ = image_data.size();  // Fill in the data size.
+    // Block sources (from the image).
+    const bool is_compressed = image_storage_mode_ != ImageHeader::kStorageModeUncompressed;
+    std::vector<std::pair<uint32_t, uint32_t>> block_sources;
+    std::vector<ImageHeader::Block> blocks;
 
-    // Write out the image + fields + methods.
-    if (!image_file->PwriteFully(image_data.data(), image_data.size(), sizeof(ImageHeader))) {
-      PLOG(ERROR) << "Failed to write image file data " << image_filename;
-      return false;
+    // Add a set of solid blocks such that no block is larger than the maximum size. A solid block
+    // is a block that must be decompressed all at once.
+    auto add_blocks = [&](uint32_t offset, uint32_t size) {
+      while (size != 0u) {
+        const uint32_t cur_size = std::min(size, compiler_options_.MaxImageBlockSize());
+        block_sources.emplace_back(offset, cur_size);
+        offset += cur_size;
+        size -= cur_size;
+      }
+    };
+
+    add_blocks(sizeof(ImageHeader), image_header->GetImageSize() - sizeof(ImageHeader));
+
+    // Checksum of compressed image data and header.
+    uint32_t image_checksum = adler32(0L, Z_NULL, 0);
+    image_checksum = adler32(image_checksum,
+                             reinterpret_cast<const uint8_t*>(image_header),
+                             sizeof(ImageHeader));
+    // Copy and compress blocks.
+    size_t out_offset = sizeof(ImageHeader);
+    for (const std::pair<uint32_t, uint32_t> block : block_sources) {
+      ArrayRef<const uint8_t> raw_image_data(image_info.image_.Begin() + block.first,
+                                             block.second);
+      std::vector<uint8_t> compressed_data;
+      ArrayRef<const uint8_t> image_data =
+          MaybeCompressData(raw_image_data, image_storage_mode_, &compressed_data);
+
+      if (!is_compressed) {
+        // For uncompressed, preserve alignment since the image will be directly mapped.
+        out_offset = block.first;
+      }
+
+      // Fill in the compressed location of the block.
+      blocks.emplace_back(ImageHeader::Block(
+          image_storage_mode_,
+          /*data_offset=*/ out_offset,
+          /*data_size=*/ image_data.size(),
+          /*image_offset=*/ block.first,
+          /*image_size=*/ block.second));
+
+      // Write out the image + fields + methods.
+      if (!image_file->PwriteFully(image_data.data(), image_data.size(), out_offset)) {
+        PLOG(ERROR) << "Failed to write image file data " << image_filename;
+        image_file->Erase();
+        return false;
+      }
+      out_offset += image_data.size();
+      image_checksum = adler32(image_checksum, image_data.data(), image_data.size());
     }
 
-    // Write out the image bitmap at the page aligned start of the image end, also uncompressed for
-    // convenience.
-    const ImageSection& bitmap_section = image_header->GetImageBitmapSection();
+    // Write the block metadata directly after the image sections.
+    // Note: This is not part of the mapped image and is not preserved after decompressing, it's
+    // only used for image loading. For this reason, only write it out for compressed images.
+    if (is_compressed) {
+      // Align up since the compressed data is not necessarily aligned.
+      out_offset = RoundUp(out_offset, alignof(ImageHeader::Block));
+      CHECK(!blocks.empty());
+      const size_t blocks_bytes = blocks.size() * sizeof(blocks[0]);
+      if (!image_file->PwriteFully(&blocks[0], blocks_bytes, out_offset)) {
+        PLOG(ERROR) << "Failed to write image blocks " << image_filename;
+        image_file->Erase();
+        return false;
+      }
+      image_header->blocks_offset_ = out_offset;
+      image_header->blocks_count_ = blocks.size();
+      out_offset += blocks_bytes;
+    }
+
+    // Data size includes everything except the bitmap.
+    image_header->data_size_ = out_offset - sizeof(ImageHeader);
+
+    // Update and write the bitmap section. Note that the bitmap section is relative to the
+    // possibly compressed image.
+    ImageSection& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap);
     // Align up since data size may be unaligned if the image is compressed.
-    size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + image_data.size(), kPageSize);
-    if (image_storage_mode_ == ImageHeader::kDefaultStorageMode) {
-      CHECK_EQ(bitmap_position_in_file, bitmap_section.Offset());
-    }
+    out_offset = RoundUp(out_offset, kPageSize);
+    bitmap_section = ImageSection(out_offset, bitmap_section.Size());
+
     if (!image_file->PwriteFully(image_info.image_bitmap_->Begin(),
                                  bitmap_section.Size(),
-                                 bitmap_position_in_file)) {
+                                 bitmap_section.Offset())) {
       PLOG(ERROR) << "Failed to write image file bitmap " << image_filename;
       return false;
     }
@@ -781,22 +842,17 @@
       return false;
     }
 
-    // Calculate the image checksum.
-    uint32_t image_checksum = adler32(0L, Z_NULL, 0);
-    image_checksum = adler32(image_checksum,
-                             reinterpret_cast<const uint8_t*>(image_header),
-                             sizeof(ImageHeader));
-    image_checksum = adler32(image_checksum, image_data.data(), image_data.size());
+    // Calculate the image checksum of the remaining data.
     image_checksum = adler32(image_checksum,
                              reinterpret_cast<const uint8_t*>(image_info.image_bitmap_->Begin()),
                              bitmap_section.Size());
     image_header->SetImageChecksum(image_checksum);
 
     if (VLOG_IS_ON(compiler)) {
-      size_t separately_written_section_size = bitmap_section.Size() + sizeof(ImageHeader);
-
-      size_t total_uncompressed_size = raw_image_data.size() + separately_written_section_size,
-             total_compressed_size   = image_data.size() + separately_written_section_size;
+      const size_t separately_written_section_size = bitmap_section.Size();
+      const size_t total_uncompressed_size = image_info.image_size_ +
+          separately_written_section_size;
+      const size_t total_compressed_size = out_offset + separately_written_section_size;
 
       VLOG(compiler) << "Dex2Oat:uncompressedImageSize = " << total_uncompressed_size;
       if (total_uncompressed_size != total_compressed_size) {
@@ -804,8 +860,8 @@
       }
     }
 
-    CHECK_EQ(bitmap_position_in_file + bitmap_section.Size(),
-             static_cast<size_t>(image_file->GetLength()));
+    CHECK_EQ(bitmap_section.End(), static_cast<size_t>(image_file->GetLength()))
+        << "Bitmap should be at the end of the file";
 
     // Write header last in case the compiler gets killed in the middle of image writing.
     // We do not want to have a corrupted image with a valid header.
@@ -2574,9 +2630,7 @@
       PointerToLowMemUInt32(oat_file_end),
       boot_image_begin,
       boot_oat_end - boot_image_begin,
-      static_cast<uint32_t>(target_ptr_size_),
-      image_storage_mode_,
-      /*data_size*/0u);
+      static_cast<uint32_t>(target_ptr_size_));
 }
 
 ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) {
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 2b97fb4..acf0f94 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -97,6 +97,11 @@
   UsageError("       oat file is up to date. Defaults to $ANDROID_ROOT/framework/boot.art.");
   UsageError("       Example: --image=/system/framework/boot.art");
   UsageError("");
+  UsageError("  --runtime-arg <argument>: used to specify various arguments for the runtime,");
+  UsageError("      such as initial heap size, maximum heap size, and verbose output.");
+  UsageError("      Use a separate --runtime-arg switch for each argument.");
+  UsageError("      Example: --runtime-arg -Xms256m");
+  UsageError("");
   UsageError("  --android-data=<directory>: optional, the directory which should be used as");
   UsageError("       android-data. By default ANDROID_DATA env variable is used.");
   UsageError("");
@@ -168,6 +173,12 @@
         }
       } else if (option.starts_with("--image=")) {
         image_ = option.substr(strlen("--image=")).ToString();
+      } else if (option == "--runtime-arg") {
+        if (i + 1 == argc) {
+          Usage("Missing argument for --runtime-arg\n");
+        }
+        ++i;
+        runtime_args_.push_back(argv[i]);
       } else if (option.starts_with("--android-data=")) {
         // Overwrite android-data if needed (oat file assistant relies on a valid directory to
         // compute dalvik-cache folder). This is mostly used in tests.
@@ -218,10 +229,14 @@
     RuntimeOptions options;
     // The image could be custom, so make sure we explicitly pass it.
     std::string img = "-Ximage:" + image_;
-    options.push_back(std::make_pair(img.c_str(), nullptr));
+    options.push_back(std::make_pair(img, nullptr));
     // The instruction set of the image should match the instruction set we will test.
     const void* isa_opt = reinterpret_cast<const void*>(GetInstructionSetString(isa_));
     options.push_back(std::make_pair("imageinstructionset", isa_opt));
+    // Explicit runtime args.
+    for (const char* runtime_arg : runtime_args_) {
+      options.push_back(std::make_pair(runtime_arg, nullptr));
+    }
      // Disable libsigchain. We don't don't need it to evaluate DexOptNeeded status.
     options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
     // Pretend we are a compiler so that we can re-use the same infrastructure to load a different
@@ -289,6 +304,7 @@
   bool assume_profile_changed_;
   bool downgrade_;
   std::string image_;
+  std::vector<const char*> runtime_args_;
   int oat_fd_ = -1;
   int vdex_fd_ = -1;
   // File descriptor corresponding to apk, dex_file, or zip.
diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h
index 8e7269c..9ea01d7 100644
--- a/libartbase/base/hiddenapi_flags.h
+++ b/libartbase/base/hiddenapi_flags.h
@@ -58,6 +58,8 @@
     "greylist-max-o",
   };
 
+  static constexpr const char* kInvalidName = "invalid";
+
   static constexpr SdkVersion kMaxSdkVersions[] {
     /* whitelist */ SdkVersion::kMax,
     /* greylist */ SdkVersion::kMax,
@@ -70,7 +72,7 @@
 
   explicit ApiList(Value value) : value_(value) {}
 
-  const Value value_;
+  Value value_;
 
  public:
   static ApiList Whitelist() { return ApiList(Value::kWhitelist); }
@@ -87,6 +89,14 @@
     return Invalid();
   }
 
+  // Decodes ApiList from its integer value.
+  static ApiList FromIntValue(IntValueType int_value) {
+    if (MinValue().GetIntValue() <= int_value && int_value <= MaxValue().GetIntValue()) {
+      return ApiList(static_cast<Value>(int_value));
+    }
+    return Invalid();
+  }
+
   // Returns the ApiList with a given name.
   static ApiList FromName(const std::string& str) {
     for (IntValueType i = MinValue().GetIntValue(); i <= MaxValue().GetIntValue(); i++) {
@@ -108,7 +118,7 @@
     return static_cast<IntValueType>(value_);
   }
 
-  const char* GetName() const { return kNames[GetIntValue()]; }
+  const char* GetName() const { return IsValid() ? kNames[GetIntValue()]: kInvalidName; }
 
   SdkVersion GetMaxAllowedSdkVersion() const { return kMaxSdkVersions[GetIntValue()]; }
 
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 51f6008..1c74a92 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -1936,7 +1936,7 @@
       stats_.file_bytes = file->GetLength();
       // If the image is compressed, adjust to decompressed size.
       size_t uncompressed_size = image_header_.GetImageSize() - sizeof(ImageHeader);
-      if (image_header_.GetStorageMode() == ImageHeader::kStorageModeUncompressed) {
+      if (image_header_.HasCompressedBlock()) {
         DCHECK_EQ(uncompressed_size, data_size) << "Sizes should match for uncompressed image";
       }
       stats_.file_bytes += uncompressed_size - data_size;
diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc
index 9f98f6c..c29b79c 100644
--- a/runtime/gc/collector/immune_spaces_test.cc
+++ b/runtime/gc/collector/immune_spaces_test.cc
@@ -124,9 +124,7 @@
         /*oat_file_end=*/ PointerToLowMemUInt32(oat_map.Begin() + oat_size),
         /*boot_image_begin=*/ 0u,
         /*boot_image_size=*/ 0u,
-        /*pointer_size=*/ sizeof(void*),
-        ImageHeader::kStorageModeUncompressed,
-        /*data_size=*/ 0u);
+        /*pointer_size=*/ sizeof(void*));
     return new DummyImageSpace(std::move(image_map),
                                std::move(live_bitmap),
                                std::move(oat_file),
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 085799c..c772bda 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -16,7 +16,6 @@
 
 #include "image_space.h"
 
-#include <lz4.h>
 #include <sys/statvfs.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -466,9 +465,10 @@
     // Check that the file is larger or equal to the header size + data size.
     const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
     if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) {
-      *error_msg = StringPrintf("Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
-                                image_file_size,
-                                sizeof(ImageHeader) + image_header->GetDataSize());
+      *error_msg = StringPrintf(
+          "Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
+           image_file_size,
+           static_cast<uint64_t>(sizeof(ImageHeader) + image_header->GetDataSize()));
       return nullptr;
     }
 
@@ -588,8 +588,9 @@
                               /*inout*/MemMap* image_reservation,
                               /*out*/std::string* error_msg) {
     TimingLogger::ScopedTiming timing("MapImageFile", logger);
-    const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode();
-    if (storage_mode == ImageHeader::kStorageModeUncompressed) {
+    std::string temp_error_msg;
+    const bool is_compressed = image_header.HasCompressedBlock();
+    if (!is_compressed) {
       uint8_t* address = (image_reservation != nullptr) ? image_reservation->Begin() : nullptr;
       return MemMap::MapFileAtAddress(address,
                                       image_header.GetImageSize(),
@@ -604,15 +605,6 @@
                                       error_msg);
     }
 
-    if (storage_mode != ImageHeader::kStorageModeLZ4 &&
-        storage_mode != ImageHeader::kStorageModeLZ4HC) {
-      if (error_msg != nullptr) {
-        *error_msg = StringPrintf("Invalid storage mode in image header %d",
-                                  static_cast<int>(storage_mode));
-      }
-      return MemMap::Invalid();
-    }
-
     // Reserve output and decompress into it.
     MemMap map = MemMap::MapAnonymous(image_location,
                                       image_header.GetImageSize(),
@@ -622,7 +614,6 @@
                                       error_msg);
     if (map.IsValid()) {
       const size_t stored_size = image_header.GetDataSize();
-      const size_t decompress_offset = sizeof(ImageHeader);  // Skip the header.
       MemMap temp_map = MemMap::MapFile(sizeof(ImageHeader) + stored_size,
                                         PROT_READ,
                                         MAP_PRIVATE,
@@ -637,27 +628,20 @@
       }
       memcpy(map.Begin(), &image_header, sizeof(ImageHeader));
       const uint64_t start = NanoTime();
-      // LZ4HC and LZ4 have same internal format, both use LZ4_decompress.
-      TimingLogger::ScopedTiming timing2("LZ4 decompress image", logger);
-      const size_t decompressed_size = LZ4_decompress_safe(
-          reinterpret_cast<char*>(temp_map.Begin()) + sizeof(ImageHeader),
-          reinterpret_cast<char*>(map.Begin()) + decompress_offset,
-          stored_size,
-          map.Size() - decompress_offset);
+      for (const ImageHeader::Block& block : image_header.GetBlocks(temp_map.Begin())) {
+        TimingLogger::ScopedTiming timing2("LZ4 decompress image", logger);
+        if (!block.Decompress(/*out_ptr=*/map.Begin(), /*in_ptr=*/temp_map.Begin(), error_msg)) {
+          if (error_msg != nullptr) {
+            *error_msg = "Failed to decompress image block " + *error_msg;
+          }
+          return MemMap::Invalid();
+        }
+      }
       const uint64_t time = NanoTime() - start;
       // Add one 1 ns to prevent possible divide by 0.
       VLOG(image) << "Decompressing image took " << PrettyDuration(time) << " ("
                   << PrettySize(static_cast<uint64_t>(map.Size()) * MsToNs(1000) / (time + 1))
                   << "/s)";
-      if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) {
-        if (error_msg != nullptr) {
-          *error_msg = StringPrintf(
-              "Decompressed size does not match expected image size %zu vs %zu",
-              decompressed_size + sizeof(ImageHeader),
-              image_header.GetImageSize());
-        }
-        return MemMap::Invalid();
-      }
     }
 
     return map;
@@ -766,6 +750,7 @@
 
     ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> obj,
                                   MemberOffset offset,
+
                                   bool is_static ATTRIBUTE_UNUSED) const
         NO_THREAD_SAFETY_ANALYSIS {
       // There could be overlap between ranges, we must avoid visiting the same reference twice.
diff --git a/runtime/image.cc b/runtime/image.cc
index f50c39c..ae3d8e3 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -16,6 +16,9 @@
 
 #include "image.h"
 
+#include <lz4.h>
+#include <sstream>
+
 #include "base/bit_utils.h"
 #include "base/length_prefixed_array.h"
 #include "base/utils.h"
@@ -26,7 +29,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '0', '\0' };  // Store ImtIndex.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '1', '\0' };  // Add image blocks.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
@@ -39,9 +42,7 @@
                          uint32_t oat_file_end,
                          uint32_t boot_image_begin,
                          uint32_t boot_image_size,
-                         uint32_t pointer_size,
-                         StorageMode storage_mode,
-                         size_t data_size)
+                         uint32_t pointer_size)
   : image_begin_(image_begin),
     image_size_(image_size),
     image_checksum_(0u),
@@ -53,9 +54,7 @@
     boot_image_begin_(boot_image_begin),
     boot_image_size_(boot_image_size),
     image_roots_(image_roots),
-    pointer_size_(pointer_size),
-    storage_mode_(storage_mode),
-    data_size_(data_size) {
+    pointer_size_(pointer_size) {
   CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize));
   CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize));
   CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize));
@@ -144,4 +143,34 @@
   return ConvertToPointerSize(pointer_size_);
 }
 
+bool ImageHeader::Block::Decompress(uint8_t* out_ptr,
+                                    const uint8_t* in_ptr,
+                                    std::string* error_msg) const {
+  switch (storage_mode_) {
+    case kStorageModeUncompressed: {
+      CHECK_EQ(image_size_, data_size_);
+      memcpy(out_ptr + image_offset_, in_ptr + data_offset_, data_size_);
+      break;
+    }
+    case kStorageModeLZ4:
+    case kStorageModeLZ4HC: {
+      // LZ4HC and LZ4 have same internal format, both use LZ4_decompress.
+      const size_t decompressed_size = LZ4_decompress_safe(
+          reinterpret_cast<const char*>(in_ptr) + data_offset_,
+          reinterpret_cast<char*>(out_ptr) + image_offset_,
+          data_size_,
+          image_size_);
+      CHECK_EQ(decompressed_size, image_size_);
+      break;
+    }
+    default: {
+      if (error_msg != nullptr) {
+        *error_msg = (std::ostringstream() << "Invalid image format " << storage_mode_).str();
+      }
+      return false;
+    }
+  }
+  return true;
+}
+
 }  // namespace art
diff --git a/runtime/image.h b/runtime/image.h
index f33b9b2..76fb3b7 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -21,6 +21,7 @@
 
 #include "base/enums.h"
 #include "base/globals.h"
+#include "base/iteration_range.h"
 #include "mirror/object.h"
 
 namespace art {
@@ -82,8 +83,10 @@
   uint32_t size_;
 };
 
-// header of image files written by ImageWriter, read and validated by Space.
-class PACKED(4) ImageHeader {
+// Header of image files written by ImageWriter, read and validated by Space.
+// Packed to object alignment since the first object follows directly after the header.
+static_assert(kObjectAlignment == 8, "Alignment check");
+class PACKED(8) ImageHeader {
  public:
   enum StorageMode : uint32_t {
     kStorageModeUncompressed,
@@ -93,8 +96,40 @@
   };
   static constexpr StorageMode kDefaultStorageMode = kStorageModeUncompressed;
 
-  ImageHeader() {}
+  // Solid block of the image. May be compressed or uncompressed.
+  class PACKED(4) Block final {
+   public:
+    Block(StorageMode storage_mode,
+          uint32_t data_offset,
+          uint32_t data_size,
+          uint32_t image_offset,
+          uint32_t image_size)
+        : storage_mode_(storage_mode),
+          data_offset_(data_offset),
+          data_size_(data_size),
+          image_offset_(image_offset),
+          image_size_(image_size) {}
 
+    bool Decompress(uint8_t* out_ptr, const uint8_t* in_ptr, std::string* error_msg) const;
+
+    StorageMode GetStorageMode() const {
+      return storage_mode_;
+    }
+
+   private:
+    // Storage method for the image, the image may be compressed.
+    StorageMode storage_mode_ = kDefaultStorageMode;
+
+    // Compressed offset and size.
+    uint32_t data_offset_ = 0u;
+    uint32_t data_size_ = 0u;
+
+    // Image offset and size (decompressed or mapped location).
+    uint32_t image_offset_ = 0u;
+    uint32_t image_size_ = 0u;
+  };
+
+  ImageHeader() {}
   ImageHeader(uint32_t image_begin,
               uint32_t image_size,
               ImageSection* sections,
@@ -106,9 +141,7 @@
               uint32_t oat_file_end,
               uint32_t boot_image_begin,
               uint32_t boot_image_size,
-              uint32_t pointer_size,
-              StorageMode storage_mode,
-              size_t data_size);
+              uint32_t pointer_size);
 
   bool IsValid() const;
   const char* GetMagic() const;
@@ -231,6 +264,11 @@
 
   ArtMethod* GetImageMethod(ImageMethod index) const;
 
+  ImageSection& GetImageSection(ImageSections index) {
+    DCHECK_LT(static_cast<size_t>(index), kSectionCount);
+    return sections_[index];
+  }
+
   const ImageSection& GetImageSection(ImageSections index) const {
     DCHECK_LT(static_cast<size_t>(index), kSectionCount);
     return sections_[index];
@@ -304,10 +342,6 @@
     return boot_image_size_;
   }
 
-  StorageMode GetStorageMode() const {
-    return storage_mode_;
-  }
-
   uint64_t GetDataSize() const {
     return data_size_;
   }
@@ -345,6 +379,24 @@
                                     uint8_t* base,
                                     PointerSize pointer_size) const;
 
+  IterationRange<const Block*> GetBlocks() const {
+    return GetBlocks(GetImageBegin());
+  }
+
+  IterationRange<const Block*> GetBlocks(const uint8_t* image_begin) const {
+    const Block* begin = reinterpret_cast<const Block*>(image_begin + blocks_offset_);
+    return {begin, begin + blocks_count_};
+  }
+
+  // Return true if the image has any compressed blocks.
+  bool HasCompressedBlock() const {
+    return blocks_count_ != 0u;
+  }
+
+  uint32_t GetBlockCount() const {
+    return blocks_count_;
+  }
+
  private:
   static const uint8_t kImageMagic[4];
   static const uint8_t kImageVersion[4];
@@ -404,13 +456,14 @@
   // Image methods, may be inside of the boot image for app images.
   uint64_t image_methods_[kImageMethodsCount];
 
-  // Storage method for the image, the image may be compressed.
-  StorageMode storage_mode_ = kDefaultStorageMode;
-
   // Data size for the image data excluding the bitmap and the header. For compressed images, this
   // is the compressed size in the file.
   uint32_t data_size_ = 0u;
 
+  // Image blocks, only used for compressed images.
+  uint32_t blocks_offset_ = 0u;
+  uint32_t blocks_count_ = 0u;
+
   friend class linker::ImageWriter;
 };
 
diff --git a/test/674-hiddenapi/api-blacklist.txt b/test/674-hiddenapi/api-blacklist.txt
deleted file mode 100644
index 4a67fb8..0000000
--- a/test/674-hiddenapi/api-blacklist.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-LNullaryConstructorBlacklist;-><init>()V
-LParentClass;->fieldPublicBlacklist:I
-LParentClass;->fieldPublicBlacklistB:I
-LParentClass;->fieldPackageBlacklist:I
-LParentClass;->fieldProtectedBlacklist:I
-LParentClass;->fieldPrivateBlacklist:I
-LParentClass;->fieldPublicStaticBlacklist:I
-LParentClass;->fieldPublicStaticBlacklistB:I
-LParentClass;->fieldPackageStaticBlacklist:I
-LParentClass;->fieldProtectedStaticBlacklist:I
-LParentClass;->fieldPrivateStaticBlacklist:I
-LParentClass;->methodPublicBlacklist()I
-LParentClass;->methodPackageBlacklist()I
-LParentClass;->methodProtectedBlacklist()I
-LParentClass;->methodPrivateBlacklist()I
-LParentClass;->methodPublicStaticBlacklist()I
-LParentClass;->methodPackageStaticBlacklist()I
-LParentClass;->methodProtectedStaticBlacklist()I
-LParentClass;->methodPrivateStaticBlacklist()I
-LParentClass;-><init>(IC)V
-LParentClass;-><init>(FC)V
-LParentClass;-><init>(JC)V
-LParentClass;-><init>(DC)V
-LParentInterface;->fieldPublicStaticBlacklist:I
-LParentInterface;->methodPublicBlacklist()I
-LParentInterface;->methodPublicStaticBlacklist()I
-LParentInterface;->methodPublicDefaultBlacklist()I
\ No newline at end of file
diff --git a/test/674-hiddenapi/api-dark-greylist.txt b/test/674-hiddenapi/api-dark-greylist.txt
deleted file mode 100644
index e010a0a..0000000
--- a/test/674-hiddenapi/api-dark-greylist.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-LNullaryConstructorDarkGreylist;-><init>()V
-LParentClass;->fieldPublicDarkGreylist:I
-LParentClass;->fieldPublicDarkGreylistB:I
-LParentClass;->fieldPackageDarkGreylist:I
-LParentClass;->fieldProtectedDarkGreylist:I
-LParentClass;->fieldPrivateDarkGreylist:I
-LParentClass;->fieldPublicStaticDarkGreylist:I
-LParentClass;->fieldPublicStaticDarkGreylistB:I
-LParentClass;->fieldPackageStaticDarkGreylist:I
-LParentClass;->fieldProtectedStaticDarkGreylist:I
-LParentClass;->fieldPrivateStaticDarkGreylist:I
-LParentClass;->methodPublicDarkGreylist()I
-LParentClass;->methodPackageDarkGreylist()I
-LParentClass;->methodProtectedDarkGreylist()I
-LParentClass;->methodPrivateDarkGreylist()I
-LParentClass;->methodPublicStaticDarkGreylist()I
-LParentClass;->methodPackageStaticDarkGreylist()I
-LParentClass;->methodProtectedStaticDarkGreylist()I
-LParentClass;->methodPrivateStaticDarkGreylist()I
-LParentClass;-><init>(IB)V
-LParentClass;-><init>(FB)V
-LParentClass;-><init>(JB)V
-LParentClass;-><init>(DB)V
-LParentInterface;->fieldPublicStaticDarkGreylist:I
-LParentInterface;->methodPublicDarkGreylist()I
-LParentInterface;->methodPublicStaticDarkGreylist()I
-LParentInterface;->methodPublicDefaultDarkGreylist()I
\ No newline at end of file
diff --git a/test/674-hiddenapi/api-light-greylist.txt b/test/674-hiddenapi/api-light-greylist.txt
deleted file mode 100644
index 4be793f..0000000
--- a/test/674-hiddenapi/api-light-greylist.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-LNullaryConstructorLightGreylist;-><init>()V
-LParentClass;->fieldPublicLightGreylist:I
-LParentClass;->fieldPublicLightGreylistB:I
-LParentClass;->fieldPackageLightGreylist:I
-LParentClass;->fieldProtectedLightGreylist:I
-LParentClass;->fieldPrivateLightGreylist:I
-LParentClass;->fieldPublicStaticLightGreylist:I
-LParentClass;->fieldPublicStaticLightGreylistB:I
-LParentClass;->fieldPackageStaticLightGreylist:I
-LParentClass;->fieldProtectedStaticLightGreylist:I
-LParentClass;->fieldPrivateStaticLightGreylist:I
-LParentClass;->methodPublicLightGreylist()I
-LParentClass;->methodPackageLightGreylist()I
-LParentClass;->methodProtectedLightGreylist()I
-LParentClass;->methodPrivateLightGreylist()I
-LParentClass;->methodPublicStaticLightGreylist()I
-LParentClass;->methodPackageStaticLightGreylist()I
-LParentClass;->methodProtectedStaticLightGreylist()I
-LParentClass;->methodPrivateStaticLightGreylist()I
-LParentClass;-><init>(IZ)V
-LParentClass;-><init>(FZ)V
-LParentClass;-><init>(JZ)V
-LParentClass;-><init>(DZ)V
-LParentInterface;->fieldPublicStaticLightGreylist:I
-LParentInterface;->methodPublicLightGreylist()I
-LParentInterface;->methodPublicStaticLightGreylist()I
-LParentInterface;->methodPublicDefaultLightGreylist()I
\ No newline at end of file
diff --git a/test/674-hiddenapi/hiddenapi-flags.csv b/test/674-hiddenapi/hiddenapi-flags.csv
new file mode 100644
index 0000000..d875bdf
--- /dev/null
+++ b/test/674-hiddenapi/hiddenapi-flags.csv
@@ -0,0 +1,81 @@
+LNullaryConstructorBlacklist;-><init>()V,blacklist
+LNullaryConstructorDarkGreylist;-><init>()V,greylist-max-o
+LNullaryConstructorLightGreylist;-><init>()V,greylist
+LParentClass;->fieldPackageBlacklist:I,blacklist
+LParentClass;->fieldPackageDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPackageLightGreylist:I,greylist
+LParentClass;->fieldPackageStaticBlacklist:I,blacklist
+LParentClass;->fieldPackageStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPackageStaticLightGreylist:I,greylist
+LParentClass;->fieldPrivateBlacklist:I,blacklist
+LParentClass;->fieldPrivateDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPrivateLightGreylist:I,greylist
+LParentClass;->fieldPrivateStaticBlacklist:I,blacklist
+LParentClass;->fieldPrivateStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPrivateStaticLightGreylist:I,greylist
+LParentClass;->fieldProtectedBlacklist:I,blacklist
+LParentClass;->fieldProtectedDarkGreylist:I,greylist-max-o
+LParentClass;->fieldProtectedLightGreylist:I,greylist
+LParentClass;->fieldProtectedStaticBlacklist:I,blacklist
+LParentClass;->fieldProtectedStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldProtectedStaticLightGreylist:I,greylist
+LParentClass;->fieldPublicBlacklistB:I,blacklist
+LParentClass;->fieldPublicBlacklist:I,blacklist
+LParentClass;->fieldPublicDarkGreylistB:I,greylist-max-o
+LParentClass;->fieldPublicDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPublicLightGreylistB:I,greylist
+LParentClass;->fieldPublicLightGreylist:I,greylist
+LParentClass;->fieldPublicStaticBlacklistB:I,blacklist
+LParentClass;->fieldPublicStaticBlacklist:I,blacklist
+LParentClass;->fieldPublicStaticDarkGreylistB:I,greylist-max-o
+LParentClass;->fieldPublicStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPublicStaticLightGreylistB:I,greylist
+LParentClass;->fieldPublicStaticLightGreylist:I,greylist
+LParentClass;-><init>(DB)V,greylist-max-o
+LParentClass;-><init>(DC)V,blacklist
+LParentClass;-><init>(DZ)V,greylist
+LParentClass;-><init>(FB)V,greylist-max-o
+LParentClass;-><init>(FC)V,blacklist
+LParentClass;-><init>(FZ)V,greylist
+LParentClass;-><init>(IB)V,greylist-max-o
+LParentClass;-><init>(IC)V,blacklist
+LParentClass;-><init>(IZ)V,greylist
+LParentClass;-><init>(JB)V,greylist-max-o
+LParentClass;-><init>(JC)V,blacklist
+LParentClass;-><init>(JZ)V,greylist
+LParentClass;->methodPackageBlacklist()I,blacklist
+LParentClass;->methodPackageDarkGreylist()I,greylist-max-o
+LParentClass;->methodPackageLightGreylist()I,greylist
+LParentClass;->methodPackageStaticBlacklist()I,blacklist
+LParentClass;->methodPackageStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodPackageStaticLightGreylist()I,greylist
+LParentClass;->methodPrivateBlacklist()I,blacklist
+LParentClass;->methodPrivateDarkGreylist()I,greylist-max-o
+LParentClass;->methodPrivateLightGreylist()I,greylist
+LParentClass;->methodPrivateStaticBlacklist()I,blacklist
+LParentClass;->methodPrivateStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodPrivateStaticLightGreylist()I,greylist
+LParentClass;->methodProtectedBlacklist()I,blacklist
+LParentClass;->methodProtectedDarkGreylist()I,greylist-max-o
+LParentClass;->methodProtectedLightGreylist()I,greylist
+LParentClass;->methodProtectedStaticBlacklist()I,blacklist
+LParentClass;->methodProtectedStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodProtectedStaticLightGreylist()I,greylist
+LParentClass;->methodPublicBlacklist()I,blacklist
+LParentClass;->methodPublicDarkGreylist()I,greylist-max-o
+LParentClass;->methodPublicLightGreylist()I,greylist
+LParentClass;->methodPublicStaticBlacklist()I,blacklist
+LParentClass;->methodPublicStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodPublicStaticLightGreylist()I,greylist
+LParentInterface;->fieldPublicStaticBlacklist:I,blacklist
+LParentInterface;->fieldPublicStaticDarkGreylist:I,greylist-max-o
+LParentInterface;->fieldPublicStaticLightGreylist:I,greylist
+LParentInterface;->methodPublicBlacklist()I,blacklist
+LParentInterface;->methodPublicDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicDefaultBlacklist()I,blacklist
+LParentInterface;->methodPublicDefaultDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicDefaultLightGreylist()I,greylist
+LParentInterface;->methodPublicLightGreylist()I,greylist
+LParentInterface;->methodPublicStaticBlacklist()I,blacklist
+LParentInterface;->methodPublicStaticDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicStaticLightGreylist()I,greylist
diff --git a/test/999-redefine-hiddenapi/api-blacklist.txt b/test/999-redefine-hiddenapi/api-blacklist.txt
deleted file mode 100644
index 63e37aa..0000000
--- a/test/999-redefine-hiddenapi/api-blacklist.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Lart/Test999;->foo()V
-Lart/Test999;->bar:I
diff --git a/test/999-redefine-hiddenapi/hiddenapi-flags.csv b/test/999-redefine-hiddenapi/hiddenapi-flags.csv
new file mode 100644
index 0000000..7f632d3
--- /dev/null
+++ b/test/999-redefine-hiddenapi/hiddenapi-flags.csv
@@ -0,0 +1,2 @@
+Lart/Test999;->foo()V,blacklist
+Lart/Test999;->bar:I,blacklist
diff --git a/test/999-redefine-hiddenapi/src-redefine/gen.sh b/test/999-redefine-hiddenapi/src-redefine/gen.sh
index f78a025..b5e2aea 100755
--- a/test/999-redefine-hiddenapi/src-redefine/gen.sh
+++ b/test/999-redefine-hiddenapi/src-redefine/gen.sh
@@ -25,7 +25,7 @@
     d8 --output . "$TMP/${CLASS}.class" &&
     hiddenapi encode --input-dex="$TMP/classes.dex" \
                      --output-dex="$TMP/classes-hiddenapi.dex" \
-                     --flags="$DIR/../hiddenapi-flags.csv" \
+                     --api-flags="$DIR/../hiddenapi-flags.csv" \
                      --no-force-assign-all)
 
 echo '  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode('
diff --git a/test/etc/default-build b/test/etc/default-build
index 3e6699a..5eba804 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -92,7 +92,7 @@
   HAS_SRC_DEX2OAT_UNRESOLVED=false
 fi
 
-if [ -f api-light-greylist.txt -o -f api-dark-greylist.txt -o -f api-blacklist.txt ]; then
+if [ -f hiddenapi-flags.csv ]; then
   HAS_HIDDENAPI_SPEC=true
 else
   HAS_HIDDENAPI_SPEC=false
@@ -327,15 +327,8 @@
     args+=("--output-dex=$1")
     shift
   done
-  if [ -f api-light-greylist.txt ]; then
-    args+=("--light-greylist=api-light-greylist.txt")
-  fi
-  if [ -f api-dark-greylist.txt ]; then
-    args+=("--dark-greylist=api-dark-greylist.txt")
-  fi
-  if [ -f api-blacklist.txt ]; then
-    args+=("--blacklist=api-blacklist.txt")
-  fi
+  args+=("--api-flags=hiddenapi-flags.csv")
+  args+=("--no-force-assign-all")
   ${HIDDENAPI} "${args[@]}"
 }
 
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationConsumer.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationConsumer.java
new file mode 100644
index 0000000..0f5f413
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationConsumer.java
@@ -0,0 +1,18 @@
+package com.android.class2greylist;
+
+import java.util.Map;
+import java.util.Set;
+
+public interface AnnotationConsumer {
+    /**
+     * Handle a parsed annotation for a class member.
+     *
+     * @param apiSignature Signature of the class member.
+     * @param annotationProperties Map of stringified properties of this annotation.
+     * @param parsedFlags Array of flags parsed from the annotation for this member.
+     */
+    public void consume(String apiSignature, Map<String, String> annotationProperties,
+            Set<String> parsedFlags);
+
+    public void close();
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java
index 92d2ab6..ba1f583 100644
--- a/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java
@@ -1,11 +1,27 @@
 package com.android.class2greylist;
 
+import java.util.Map;
+import java.util.HashMap;
+
 import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ElementValuePair;
+
 
 /**
- * Interface for an annotation handler, which handle individual annotations on
+ * Base class for an annotation handler, which handle individual annotations on
  * class members.
  */
-public interface AnnotationHandler {
-    void handleAnnotation(AnnotationEntry annotation, AnnotationContext context);
+public abstract class AnnotationHandler {
+    abstract void handleAnnotation(AnnotationEntry annotation, AnnotationContext context);
+
+    protected Map<String, String> stringifyAnnotationProperties(AnnotationEntry annotation) {
+        Map<String, String> content = new HashMap<String, String>();
+
+        // Stringify all annotation properties.
+        for (ElementValuePair prop : annotation.getElementValuePairs()) {
+            content.put(prop.getNameString(), prop.getValue().stringifyValue());
+        }
+
+        return content;
+    }
 }
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java
new file mode 100644
index 0000000..aacd963
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java
@@ -0,0 +1,56 @@
+package com.android.class2greylist;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class AnnotationPropertyWriter implements AnnotationConsumer {
+
+    private final PrintStream mOutput;
+    private final List<Map<String, String>> mContents;
+    private final Set<String> mColumns;
+
+    public AnnotationPropertyWriter(String csvFile) throws FileNotFoundException {
+        mOutput = new PrintStream(new FileOutputStream(new File(csvFile)));
+        mContents = new ArrayList<>();
+        mColumns = new HashSet<>();
+    }
+
+    public void consume(String apiSignature, Map<String, String> annotationProperties,
+            Set<String> parsedFlags) {
+        // Clone properties map.
+        Map<String, String> contents = new HashMap(annotationProperties);
+
+        // Append the member signature.
+        contents.put("signature", apiSignature);
+
+        // Store data.
+        mColumns.addAll(contents.keySet());
+        mContents.add(contents);
+    }
+
+    public void close() {
+        // Sort columns by name and print header row.
+        List<String> columns = new ArrayList<>(mColumns);
+        columns.sort(Comparator.naturalOrder());
+        mOutput.println(columns.stream().collect(Collectors.joining(",")));
+
+        // Sort contents according to columns and print.
+        for (Map<String, String> row : mContents) {
+            mOutput.println(columns.stream().map(column -> row.getOrDefault(column, ""))
+                    .collect(Collectors.joining(",")));
+        }
+
+        // Close output.
+        mOutput.close();
+    }
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
index 870f85a..621ee11 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
@@ -42,6 +42,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -57,17 +58,28 @@
                     "Ldalvik/annotation/compat/UnsupportedAppUsage;");
     private static final Set<String> WHITELIST_ANNOTATIONS = ImmutableSet.of();
 
+    public static final String FLAG_WHITELIST = "whitelist";
+    public static final String FLAG_GREYLIST = "greylist";
+    public static final String FLAG_BLACKLIST = "blacklist";
+    public static final String FLAG_GREYLIST_MAX_O = "greylist-max-o";
+
+    private static final Map<Integer, String> TARGET_SDK_TO_LIST_MAP;
+    static {
+        Map<Integer, String> map = new HashMap<>();
+        map.put(null, FLAG_GREYLIST);
+        map.put(26, FLAG_GREYLIST_MAX_O);
+        map.put(28, FLAG_GREYLIST);
+        TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map);
+    }
+
     private final Status mStatus;
     private final String mPublicApiListFile;
-    private final String[] mPerSdkOutputFiles;
-    private final String mWhitelistFile;
+    private final String mCsvFlagsFile;
     private final String mCsvMetadataFile;
     private final String[] mJarFiles;
-    private final GreylistConsumer mOutput;
-    private final Predicate<Integer> mAllowedSdkVersions;
+    private final AnnotationConsumer mOutput;
     private final Set<String> mPublicApis;
 
-
     public static void main(String[] args) {
         Options options = new Options();
         options.addOption(OptionBuilder
@@ -76,19 +88,9 @@
                 .withDescription("Public API list file. Used to de-dupe bridge methods.")
                 .create("p"));
         options.addOption(OptionBuilder
-                .withLongOpt("write-greylist")
-                .hasArgs()
-                .withDescription(
-                        "Specify file to write greylist to. Can be specified multiple times. " +
-                        "Format is either just a filename, or \"int[,int,...]:filename\". If " +
-                        "integers are given, members with matching maxTargetSdk values are " +
-                        "written to the file; if no integer or \"none\" is given, members with " +
-                        "no maxTargetSdk are written.")
-                .create("g"));
-        options.addOption(OptionBuilder
-                .withLongOpt("write-whitelist")
+                .withLongOpt("write-flags-csv")
                 .hasArgs(1)
-                .withDescription("Specify file to write whitelist to.")
+                .withDescription("Specify file to write hiddenapi flags to.")
                 .create('w'));
         options.addOption(OptionBuilder
                 .withLongOpt("debug")
@@ -106,7 +108,7 @@
                 .hasArgs(1)
                 .withDescription("Specify a file to write API metaadata to. This is a CSV file " +
                         "containing any annotation properties for all members. Do not use in " +
-                        "conjunction with --write-greylist or --write-whitelist.")
+                        "conjunction with --write-flags-csv.")
                 .create('c'));
         options.addOption(OptionBuilder
                 .withLongOpt("help")
@@ -144,7 +146,6 @@
                 Class2Greylist c2gl = new Class2Greylist(
                         status,
                         cmd.getOptionValue('p', null),
-                        cmd.getOptionValues('g'),
                         cmd.getOptionValue('w', null),
                         cmd.getOptionValue('c', null),
                         jarFiles);
@@ -163,34 +164,18 @@
     }
 
     @VisibleForTesting
-    Class2Greylist(Status status, String publicApiListFile, String[] perSdkLevelOutputFiles,
-            String whitelistOutputFile, String csvMetadataFile, String[] jarFiles)
+    Class2Greylist(Status status, String publicApiListFile, String csvFlagsFile,
+            String csvMetadataFile, String[] jarFiles)
             throws IOException {
         mStatus = status;
         mPublicApiListFile = publicApiListFile;
-        mPerSdkOutputFiles = perSdkLevelOutputFiles;
-        mWhitelistFile = whitelistOutputFile;
+        mCsvFlagsFile = csvFlagsFile;
         mCsvMetadataFile = csvMetadataFile;
         mJarFiles = jarFiles;
         if (mCsvMetadataFile != null) {
-            mOutput = new CsvGreylistConsumer(mStatus, mCsvMetadataFile);
-            mAllowedSdkVersions = x -> true;
+            mOutput = new AnnotationPropertyWriter(mCsvMetadataFile);
         } else {
-            Map<Integer, String> outputFiles = readGreylistMap(mStatus, mPerSdkOutputFiles);
-            mOutput = new FileWritingGreylistConsumer(mStatus, outputFiles, mWhitelistFile);
-            mAllowedSdkVersions = new Predicate<Integer>(){
-                @Override
-                public boolean test(Integer i) {
-                    return outputFiles.keySet().contains(i);
-                }
-
-                @Override
-                public String toString() {
-                    // we reply on this toString behaviour for readable error messages in
-                    // GreylistAnnotationHandler
-                    return Joiner.on(",").join(outputFiles.keySet());
-                }
-            };
+            mOutput = new HiddenapiFlagsWriter(mCsvFlagsFile);
         }
 
         if (mPublicApiListFile != null) {
@@ -203,14 +188,15 @@
 
     private Map<String, AnnotationHandler> createAnnotationHandlers() {
         Builder<String, AnnotationHandler> builder = ImmutableMap.builder();
-        GreylistAnnotationHandler greylistAnnotationHandler = new GreylistAnnotationHandler(
-            mStatus, mOutput, mPublicApis, mAllowedSdkVersions);
+        UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler =
+                new UnsupportedAppUsageAnnotationHandler(
+                    mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP);
         GREYLIST_ANNOTATIONS.forEach(a -> builder.put(a, greylistAnnotationHandler));
         return builder
                 .put(CovariantReturnTypeHandler.ANNOTATION_NAME,
-                        new CovariantReturnTypeHandler(mOutput, mPublicApis))
+                        new CovariantReturnTypeHandler(mOutput, mPublicApis, FLAG_WHITELIST))
                 .put(CovariantReturnTypeMultiHandler.ANNOTATION_NAME,
-                        new CovariantReturnTypeMultiHandler(mOutput, mPublicApis))
+                        new CovariantReturnTypeMultiHandler(mOutput, mPublicApis, FLAG_WHITELIST))
                 .build();
     }
 
@@ -230,48 +216,6 @@
         mOutput.close();
     }
 
-    @VisibleForTesting
-    static Map<Integer, String> readGreylistMap(Status status, String[] argValues) {
-        Map<Integer, String> map = new HashMap<>();
-        for (String sdkFile : argValues) {
-            List<Integer> maxTargetSdks = new ArrayList<>();
-            String filename;
-            int colonPos = sdkFile.indexOf(':');
-            if (colonPos != -1) {
-                String[] targets = sdkFile.substring(0, colonPos).split(",");
-                for (String target : targets) {
-                    if ("none".equals(target)) {
-                        maxTargetSdks.add(null);
-                    } else {
-                        try {
-                            maxTargetSdks.add(Integer.valueOf(target));
-                        } catch (NumberFormatException nfe) {
-                            status.error("Not a valid integer: %s from argument value '%s'",
-                                    sdkFile.substring(0, colonPos), sdkFile);
-                        }
-                    }
-                }
-                filename = sdkFile.substring(colonPos + 1);
-                if (filename.length() == 0) {
-                    status.error("Not a valid file name: %s from argument value '%s'",
-                            filename, sdkFile);
-                }
-            } else {
-                maxTargetSdks.add(null);
-                filename = sdkFile;
-            }
-            for (Integer maxTargetSdk : maxTargetSdks) {
-                if (map.containsKey(maxTargetSdk)) {
-                    status.error("Multiple output files for maxTargetSdk %s",
-                            maxTargetSdk == null ? "none" : maxTargetSdk);
-                } else {
-                    map.put(maxTargetSdk, filename);
-                }
-            }
-        }
-        return map;
-    }
-
     private static void dumpAllMembers(Status status, String[] jarFiles) {
         for (String jarFile : jarFiles) {
             status.debug("Processing jar file %s", jarFile);
diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
index afd15b4..b8de7e9 100644
--- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
@@ -1,12 +1,18 @@
 package com.android.class2greylist;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
 
 import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.Constant;
+import org.apache.bcel.classfile.ConstantPool;
+import org.apache.bcel.classfile.ElementValue;
 import org.apache.bcel.classfile.ElementValuePair;
 import org.apache.bcel.classfile.Method;
 
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -20,19 +26,22 @@
  * <p>Methods are also validated against the public API list, to assert that
  * the annotated method is already a public API.
  */
-public class CovariantReturnTypeHandler implements AnnotationHandler {
+public class CovariantReturnTypeHandler extends AnnotationHandler {
 
     private static final String SHORT_NAME = "CovariantReturnType";
     public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType;";
 
     private static final String RETURN_TYPE = "returnType";
 
-    private final GreylistConsumer mConsumer;
+    private final AnnotationConsumer mAnnotationConsumer;
     private final Set<String> mPublicApis;
+    private final String mHiddenapiFlag;
 
-    public CovariantReturnTypeHandler(GreylistConsumer consumer, Set<String> publicApis) {
-        mConsumer = consumer;
+    public CovariantReturnTypeHandler(AnnotationConsumer consumer, Set<String> publicApis,
+            String hiddenapiFlag) {
+        mAnnotationConsumer = consumer;
         mPublicApis = publicApis;
+        mHiddenapiFlag = hiddenapiFlag;
     }
 
     @Override
@@ -74,7 +83,9 @@
                     signature, SHORT_NAME);
             return;
         }
-        mConsumer.whitelistEntry(signature);
+
+        mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation),
+                ImmutableSet.of(mHiddenapiFlag));
     }
 
     private String findReturnType(AnnotationEntry a) {
diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
index bd0bf79..f2bc525 100644
--- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
@@ -18,7 +18,7 @@
  *
  * <p>The enclosed annotations are passed to {@link CovariantReturnTypeHandler}.
  */
-public class CovariantReturnTypeMultiHandler implements AnnotationHandler {
+public class CovariantReturnTypeMultiHandler extends AnnotationHandler {
 
     public static final String ANNOTATION_NAME =
             "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;";
@@ -28,14 +28,15 @@
     private final CovariantReturnTypeHandler mWrappedHandler;
     private final String mInnerAnnotationName;
 
-    public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis) {
-        this(consumer, publicApis, CovariantReturnTypeHandler.ANNOTATION_NAME);
+    public CovariantReturnTypeMultiHandler(AnnotationConsumer consumer, Set<String> publicApis,
+            String hiddenapiFlag) {
+        this(consumer, publicApis, hiddenapiFlag, CovariantReturnTypeHandler.ANNOTATION_NAME);
     }
 
     @VisibleForTesting
-    public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis,
-            String innerAnnotationName) {
-        mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis);
+    public CovariantReturnTypeMultiHandler(AnnotationConsumer consumer, Set<String> publicApis,
+            String hiddenapiFlag, String innerAnnotationName) {
+        mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis, hiddenapiFlag);
         mInnerAnnotationName = innerAnnotationName;
     }
 
diff --git a/tools/class2greylist/src/com/android/class2greylist/CsvGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/CsvGreylistConsumer.java
deleted file mode 100644
index 7d28b31..0000000
--- a/tools/class2greylist/src/com/android/class2greylist/CsvGreylistConsumer.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.android.class2greylist;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.PrintStream;
-import java.util.Map;
-
-public class CsvGreylistConsumer implements GreylistConsumer {
-
-    private final Status mStatus;
-    private final CsvWriter mCsvWriter;
-
-    public CsvGreylistConsumer(Status status, String csvMetadataFile) throws FileNotFoundException {
-        mStatus = status;
-        mCsvWriter = new CsvWriter(
-                new PrintStream(new FileOutputStream(new File(csvMetadataFile))));
-    }
-
-    @Override
-    public void greylistEntry(String signature, Integer maxTargetSdk,
-            Map<String, String> annotationProperties) {
-        annotationProperties.put("signature", signature);
-        mCsvWriter.addRow(annotationProperties);
-    }
-
-    @Override
-    public void whitelistEntry(String signature) {
-    }
-
-    @Override
-    public void close() {
-        mCsvWriter.close();
-    }
-}
diff --git a/tools/class2greylist/src/com/android/class2greylist/CsvWriter.java b/tools/class2greylist/src/com/android/class2greylist/CsvWriter.java
deleted file mode 100644
index 3cfec30..0000000
--- a/tools/class2greylist/src/com/android/class2greylist/CsvWriter.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.android.class2greylist;
-
-import com.google.common.base.Joiner;
-
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Helper class for writing data to a CSV file.
- *
- * This class does not write anything to its output until it is closed, so it can gather a set of
- * all columns before writing the header row.
- */
-public class CsvWriter {
-
-    private final PrintStream mOutput;
-    private final ArrayList<Map<String, String>> mContents;
-    private final Set<String> mColumns;
-
-    public CsvWriter(PrintStream out) {
-        mOutput = out;
-        mContents = new ArrayList<>();
-        mColumns = new HashSet<>();
-    }
-
-    public void addRow(Map<String, String> values) {
-        mColumns.addAll(values.keySet());
-        mContents.add(values);
-    }
-
-    public void close() {
-        List<String> columns = new ArrayList<>(mColumns);
-        columns.sort(Comparator.naturalOrder());
-        mOutput.println(columns.stream().collect(Collectors.joining(",")));
-        for (Map<String, String> row : mContents) {
-            mOutput.println(columns.stream().map(column -> row.getOrDefault(column, "")).collect(
-                    Collectors.joining(",")));
-        }
-        mOutput.close();
-    }
-
-
-}
diff --git a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java
deleted file mode 100644
index b3ed1b1..0000000
--- a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.android.class2greylist;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.PrintStream;
-import java.util.HashMap;
-import java.util.Map;
-
-public class FileWritingGreylistConsumer implements GreylistConsumer {
-
-    private final Status mStatus;
-    private final Map<Integer, PrintStream> mSdkToPrintStreamMap;
-    private final PrintStream mWhitelistStream;
-
-    private static PrintStream openFile(String filename) throws FileNotFoundException {
-        if (filename == null) {
-            return null;
-        }
-        return new PrintStream(new FileOutputStream(new File(filename)));
-    }
-
-    @VisibleForTesting
-    public static Map<Integer, PrintStream> openFiles(
-            Map<Integer, String> filenames) throws FileNotFoundException {
-        Map<String, PrintStream> streamsByName = new HashMap<>();
-        Map<Integer, PrintStream> streams = new HashMap<>();
-        for (Map.Entry<Integer, String> entry : filenames.entrySet()) {
-            if (!streamsByName.containsKey(entry.getValue())) {
-                streamsByName.put(entry.getValue(), openFile(entry.getValue()));
-            }
-            streams.put(entry.getKey(), streamsByName.get(entry.getValue()));
-        }
-        return streams;
-    }
-
-    public FileWritingGreylistConsumer(Status status, Map<Integer, String> sdkToFilenameMap,
-            String whitelistFile) throws FileNotFoundException {
-        mStatus = status;
-        mSdkToPrintStreamMap = openFiles(sdkToFilenameMap);
-        mWhitelistStream = openFile(whitelistFile);
-    }
-
-    @Override
-    public void greylistEntry(
-            String signature, Integer maxTargetSdk, Map<String, String> annotationProperties) {
-        PrintStream p = mSdkToPrintStreamMap.get(maxTargetSdk);
-        if (p == null) {
-            mStatus.error("No output file for signature %s with maxTargetSdk of %d", signature,
-                    maxTargetSdk == null ? "<absent>" : maxTargetSdk.toString());
-            return;
-        }
-        p.println(signature);
-    }
-
-    @Override
-    public void whitelistEntry(String signature) {
-        if (mWhitelistStream != null) {
-            mWhitelistStream.println(signature);
-        }
-    }
-
-    @Override
-    public void close() {
-        for (PrintStream p : mSdkToPrintStreamMap.values()) {
-            p.close();
-        }
-        if (mWhitelistStream != null) {
-            mWhitelistStream.close();
-        }
-    }
-}
diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java
deleted file mode 100644
index 72c0ea4..0000000
--- a/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.android.class2greylist;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-
-import org.apache.bcel.Const;
-import org.apache.bcel.classfile.AnnotationEntry;
-import org.apache.bcel.classfile.ElementValue;
-import org.apache.bcel.classfile.ElementValuePair;
-import org.apache.bcel.classfile.FieldOrMethod;
-import org.apache.bcel.classfile.Method;
-import org.apache.bcel.classfile.SimpleElementValue;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Predicate;
-
-/**
- * Processes {@code UnsupportedAppUsage} annotations to generate greylist
- * entries.
- *
- * Any annotations with a {@link #EXPECTED_SIGNATURE} property will have their
- * generated signature verified against this, and an error will be reported if
- * it does not match. Exclusions are made for bridge methods.
- *
- * Any {@link #MAX_TARGET_SDK} properties will be validated against the given
- * set of valid values, then passed through to the greylist consumer.
- */
-public class GreylistAnnotationHandler implements AnnotationHandler {
-
-    // properties of greylist annotations:
-    private static final String EXPECTED_SIGNATURE = "expectedSignature";
-    private static final String MAX_TARGET_SDK = "maxTargetSdk";
-
-    private final Status mStatus;
-    private final Predicate<GreylistMember> mGreylistFilter;
-    private final GreylistConsumer mGreylistConsumer;
-    private final Predicate<Integer> mValidMaxTargetSdkValues;
-
-    /**
-     * Represents a member of a class file (a field or method).
-     */
-    @VisibleForTesting
-    public static class GreylistMember {
-
-        /**
-         * Signature of this member.
-         */
-        public final String signature;
-        /**
-         * Indicates if this is a synthetic bridge method.
-         */
-        public final boolean bridge;
-        /**
-         * Max target SDK of property this member, if it is set, else null.
-         *
-         * Note: even though the annotation itself specified a default value,
-         * that default value is not encoded into instances of the annotation
-         * in class files. So when no value is specified in source, it will
-         * result in null appearing in here.
-         */
-        public final Integer maxTargetSdk;
-
-        public GreylistMember(String signature, boolean bridge, Integer maxTargetSdk) {
-            this.signature = signature;
-            this.bridge = bridge;
-            this.maxTargetSdk = maxTargetSdk;
-        }
-    }
-
-    public GreylistAnnotationHandler(
-            Status status,
-            GreylistConsumer greylistConsumer,
-            Set<String> publicApis,
-            Predicate<Integer> validMaxTargetSdkValues) {
-        this(status, greylistConsumer,
-                member -> !(member.bridge && publicApis.contains(member.signature)),
-                validMaxTargetSdkValues);
-    }
-
-    @VisibleForTesting
-    public GreylistAnnotationHandler(
-            Status status,
-            GreylistConsumer greylistConsumer,
-            Predicate<GreylistMember> greylistFilter,
-            Predicate<Integer> validMaxTargetSdkValues) {
-        mStatus = status;
-        mGreylistConsumer = greylistConsumer;
-        mGreylistFilter = greylistFilter;
-        mValidMaxTargetSdkValues = validMaxTargetSdkValues;
-    }
-
-    @Override
-    public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
-        FieldOrMethod member = context.member;
-        boolean bridge = (member instanceof Method)
-                && (member.getAccessFlags() & Const.ACC_BRIDGE) != 0;
-        if (bridge) {
-            mStatus.debug("Member is a bridge");
-        }
-        String signature = context.getMemberDescriptor();
-        Integer maxTargetSdk = null;
-        Map<String, String> allValues = new HashMap<String, String>();
-        for (ElementValuePair property : annotation.getElementValuePairs()) {
-            switch (property.getNameString()) {
-                case EXPECTED_SIGNATURE:
-                    verifyExpectedSignature(context, property, signature, bridge);
-                    break;
-                case MAX_TARGET_SDK:
-                    maxTargetSdk = verifyAndGetMaxTargetSdk(context, property);
-                    break;
-            }
-            allValues.put(property.getNameString(), property.getValue().stringifyValue());
-        }
-        if (mGreylistFilter.test(new GreylistMember(signature, bridge, maxTargetSdk))) {
-            mGreylistConsumer.greylistEntry(signature, maxTargetSdk, allValues);
-        }
-    }
-
-    private void verifyExpectedSignature(AnnotationContext context, ElementValuePair property,
-            String signature, boolean isBridge) {
-        String expected = property.getValue().stringifyValue();
-        // Don't enforce for bridge methods; they're generated so won't match.
-        if (!isBridge && !signature.equals(expected)) {
-            context.reportError("Expected signature does not match generated:\n"
-                            + "Expected:  %s\n"
-                            + "Generated: %s", expected, signature);
-        }
-    }
-
-    private Integer verifyAndGetMaxTargetSdk(AnnotationContext context, ElementValuePair property) {
-        if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) {
-            context.reportError("Expected property %s to be of type int; got %d",
-                    property.getNameString(), property.getValue().getElementValueType());
-            return null;
-        }
-        int value = ((SimpleElementValue) property.getValue()).getValueInt();
-        if (!mValidMaxTargetSdkValues.test(value)) {
-            context.reportError("Invalid value for %s: got %d, expected one of [%s]",
-                    property.getNameString(),
-                    value,
-                    mValidMaxTargetSdkValues);
-            return null;
-        }
-        return value;
-    }
-
-}
diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java
deleted file mode 100644
index afded37..0000000
--- a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.android.class2greylist;
-
-import java.util.Map;
-
-public interface GreylistConsumer {
-    /**
-     * Handle a new greylist entry.
-     *
-     * @param signature Signature of the member.
-     * @param maxTargetSdk maxTargetSdk value from the annotation, or null if none set.
-     */
-    void greylistEntry(
-            String signature, Integer maxTargetSdk, Map<String, String> annotationProperties);
-
-    /**
-     * Handle a new whitelist entry.
-     *
-     * @param signature Signature of the member.
-     */
-    void whitelistEntry(String signature);
-
-    void close();
-}
diff --git a/tools/class2greylist/src/com/android/class2greylist/HiddenapiFlagsWriter.java b/tools/class2greylist/src/com/android/class2greylist/HiddenapiFlagsWriter.java
new file mode 100644
index 0000000..54ca17c
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/HiddenapiFlagsWriter.java
@@ -0,0 +1,40 @@
+package com.android.class2greylist;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class HiddenapiFlagsWriter implements AnnotationConsumer {
+
+    private final PrintStream mOutput;
+
+    public HiddenapiFlagsWriter(String csvFile) throws FileNotFoundException {
+        mOutput = new PrintStream(new FileOutputStream(new File(csvFile)));
+    }
+
+    public void consume(String apiSignature, Map<String, String> annotationProperties,
+            Set<String> parsedFlags) {
+        if (parsedFlags.size() > 0) {
+            mOutput.println(apiSignature + "," + String.join(",", asSortedList(parsedFlags)));
+        }
+    }
+
+    public void close() {
+        mOutput.close();
+    }
+
+    private static List<String> asSortedList(Set<String> s) {
+        List<String> list = new ArrayList<>(s);
+        Collections.sort(list);
+        return list;
+    }
+
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java
deleted file mode 100644
index f86ac6e..0000000
--- a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.class2greylist;
-
-import java.util.Map;
-
-public class SystemOutGreylistConsumer implements GreylistConsumer {
-    @Override
-    public void greylistEntry(
-            String signature, Integer maxTargetSdk, Map<String, String> annotationValues) {
-        System.out.println(signature);
-    }
-
-    @Override
-    public void whitelistEntry(String signature) {
-        // Ignore. This class is only used when no grey/white lists are
-        // specified, so we have nowhere to write whitelist entries.
-    }
-
-    @Override
-    public void close() {
-    }
-}
diff --git a/tools/class2greylist/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandler.java
new file mode 100644
index 0000000..d1f3e77
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandler.java
@@ -0,0 +1,134 @@
+package com.android.class2greylist;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ElementValue;
+import org.apache.bcel.classfile.ElementValuePair;
+import org.apache.bcel.classfile.FieldOrMethod;
+import org.apache.bcel.classfile.Method;
+import org.apache.bcel.classfile.SimpleElementValue;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Processes {@code UnsupportedAppUsage} annotations to generate greylist
+ * entries.
+ *
+ * Any annotations with a {@link #EXPECTED_SIGNATURE} property will have their
+ * generated signature verified against this, and an error will be reported if
+ * it does not match. Exclusions are made for bridge methods.
+ *
+ * Any {@link #MAX_TARGET_SDK} properties will be validated against the given
+ * set of valid values, then passed through to the greylist consumer.
+ */
+public class UnsupportedAppUsageAnnotationHandler extends AnnotationHandler {
+
+    // properties of greylist annotations:
+    private static final String EXPECTED_SIGNATURE_PROPERTY = "expectedSignature";
+    private static final String MAX_TARGET_SDK_PROPERTY = "maxTargetSdk";
+
+    private final Status mStatus;
+    private final Predicate<ClassMember> mClassMemberFilter;
+    private final Map<Integer, String> mSdkVersionToFlagMap;
+    private final AnnotationConsumer mAnnotationConsumer;
+
+    /**
+     * Represents a member of a class file (a field or method).
+     */
+    @VisibleForTesting
+    public static class ClassMember {
+
+        /**
+         * Signature of this class member.
+         */
+        public final String signature;
+
+        /**
+         * Indicates if this is a synthetic bridge method.
+         */
+        public final boolean isBridgeMethod;
+
+        public ClassMember(String signature, boolean isBridgeMethod) {
+            this.signature = signature;
+            this.isBridgeMethod = isBridgeMethod;
+        }
+    }
+
+    public UnsupportedAppUsageAnnotationHandler(Status status,
+            AnnotationConsumer annotationConsumer, Set<String> publicApis,
+            Map<Integer, String> sdkVersionToFlagMap) {
+        this(status, annotationConsumer,
+                member -> !(member.isBridgeMethod && publicApis.contains(member.signature)),
+                sdkVersionToFlagMap);
+    }
+
+    @VisibleForTesting
+    public UnsupportedAppUsageAnnotationHandler(Status status,
+            AnnotationConsumer annotationConsumer, Predicate<ClassMember> memberFilter,
+            Map<Integer, String> sdkVersionToFlagMap) {
+        mStatus = status;
+        mAnnotationConsumer = annotationConsumer;
+        mClassMemberFilter = memberFilter;
+        mSdkVersionToFlagMap = sdkVersionToFlagMap;
+    }
+
+    @Override
+    public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
+        FieldOrMethod member = context.member;
+
+        boolean isBridgeMethod = (member instanceof Method) &&
+                (member.getAccessFlags() & Const.ACC_BRIDGE) != 0;
+        if (isBridgeMethod) {
+            mStatus.debug("Member is a bridge method");
+        }
+
+        String signature = context.getMemberDescriptor();
+        Integer maxTargetSdk = null;
+
+        for (ElementValuePair property : annotation.getElementValuePairs()) {
+            switch (property.getNameString()) {
+                case EXPECTED_SIGNATURE_PROPERTY:
+                    String expected = property.getValue().stringifyValue();
+                    // Don't enforce for bridge methods; they're generated so won't match.
+                    if (!isBridgeMethod && !signature.equals(expected)) {
+                        context.reportError("Expected signature does not match generated:\n"
+                                        + "Expected:  %s\n"
+                                        + "Generated: %s", expected, signature);
+                        return;
+                    }
+                    break;
+                case MAX_TARGET_SDK_PROPERTY:
+                    if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) {
+                        context.reportError("Expected property %s to be of type int; got %d",
+                                property.getNameString(),
+                                property.getValue().getElementValueType());
+                        return;
+                    }
+
+                    maxTargetSdk = ((SimpleElementValue) property.getValue()).getValueInt();
+                    break;
+            }
+        }
+
+        // Verify that maxTargetSdk is valid.
+        if (!mSdkVersionToFlagMap.containsKey(maxTargetSdk)) {
+            context.reportError("Invalid value for %s: got %d, expected one of [%s]",
+                    MAX_TARGET_SDK_PROPERTY,
+                    maxTargetSdk,
+                    mSdkVersionToFlagMap.keySet());
+            return;
+        }
+
+        // Consume this annotation if it matches the predicate.
+        if (mClassMemberFilter.test(new ClassMember(signature, isBridgeMethod))) {
+            mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation),
+                    ImmutableSet.of(mSdkVersionToFlagMap.get(maxTargetSdk)));
+        }
+    }
+}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java
index 8f4a76f..65ebbf0 100644
--- a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java
+++ b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java
@@ -36,14 +36,14 @@
     public TestName mTestName = new TestName();
 
     protected Javac mJavac;
-    protected GreylistConsumer mConsumer;
+    protected AnnotationConsumer mConsumer;
     protected Status mStatus;
 
     @Before
     public void baseSetup() throws IOException {
         System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
                 mTestName.getMethodName()));
-        mConsumer = mock(GreylistConsumer.class);
+        mConsumer = mock(AnnotationConsumer.class);
         mStatus = mock(Status.class, withSettings().verboseLogging());
         mJavac = new Javac();
     }
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java
deleted file mode 100644
index b87a5b1..0000000
--- a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.class2greylist;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.mockito.Mock;
-
-import java.io.IOException;
-import java.util.Map;
-
-public class Class2GreylistTest {
-
-    @Mock
-    Status mStatus;
-    @Rule
-    public TestName mTestName = new TestName();
-
-    @Before
-    public void setup() throws IOException {
-        System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
-                mTestName.getMethodName()));
-        initMocks(this);
-    }
-
-    @Test
-    public void testReadGreylistMap() throws IOException {
-        Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus,
-                new String[]{"noApi", "1:apiOne", "3:apiThree"});
-        verifyZeroInteractions(mStatus);
-        assertThat(map).containsExactly(null, "noApi", 1, "apiOne", 3, "apiThree");
-    }
-
-    @Test
-    public void testReadGreylistMapNone() throws IOException {
-        Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus,
-                new String[]{"none:noApi"});
-        verifyZeroInteractions(mStatus);
-        assertThat(map).containsExactly(null, "noApi");
-    }
-
-    @Test
-    public void testReadGreylistMapMulti() throws IOException {
-        Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus,
-                new String[]{"1,none:noOr1Api", "3:apiThree"});
-        verifyZeroInteractions(mStatus);
-        assertThat(map).containsExactly(null, "noOr1Api", 1, "noOr1Api", 3, "apiThree");
-    }
-
-    @Test
-    public void testReadGreylistMapMulti2() throws IOException {
-        Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus,
-                new String[]{"1,none,2,3,4:allApi"});
-        verifyZeroInteractions(mStatus);
-        assertThat(map).containsExactly(
-                null, "allApi", 1, "allApi", 2, "allApi", 3, "allApi", 4, "allApi");
-    }
-
-    @Test
-    public void testReadGreylistMapDuplicate() throws IOException {
-        Class2Greylist.readGreylistMap(mStatus,
-                new String[]{"noApi", "1:apiOne", "1:anotherOne"});
-        verify(mStatus, atLeastOnce()).error(any(), any());
-    }
-
-    @Test
-    public void testReadGreylistMapDuplicateNoApi() {
-        Class2Greylist.readGreylistMap(mStatus,
-                new String[]{"noApi", "anotherNoApi", "1:apiOne"});
-        verify(mStatus, atLeastOnce()).error(any(), any());
-    }
-
-    @Test
-    public void testReadGreylistMapInvalidInt() throws IOException {
-        Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "a:apiOne"});
-        verify(mStatus, atLeastOnce()).error(any(), any());
-    }
-
-    @Test
-    public void testReadGreylistMapNoFilename() throws IOException {
-        Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "1:"});
-        verify(mStatus, atLeastOnce()).error(any(), any());
-    }
-}
-
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
index 10fae9b..9d2f014 100644
--- a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
+++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
@@ -40,6 +40,7 @@
 public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase {
 
     private static final String ANNOTATION = "Lannotation/Annotation;";
+    private static final String FLAG = "test-flag";
 
     @Before
     public void setup() throws IOException {
@@ -72,11 +73,13 @@
                 ImmutableMap.of(ANNOTATION,
                         new CovariantReturnTypeHandler(
                                 mConsumer,
-                                ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;")));
+                                ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"),
+                                FLAG));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         assertNoErrors();
-        verify(mConsumer, times(1)).whitelistEntry(eq("La/b/Class;->method()Ljava/lang/Integer;"));
+        verify(mConsumer, times(1)).consume(
+                eq("La/b/Class;->method()Ljava/lang/Integer;"), any(), eq(ImmutableSet.of(FLAG)));
     }
 
     @Test
@@ -94,7 +97,8 @@
                 ImmutableMap.of(ANNOTATION,
                         new CovariantReturnTypeHandler(
                                 mConsumer,
-                                emptySet()));
+                                emptySet(),
+                                FLAG));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         verify(mStatus, atLeastOnce()).error(any(), any());
@@ -118,7 +122,8 @@
                                 ImmutableSet.of(
                                         "La/b/Class;->method()Ljava/lang/String;",
                                         "La/b/Class;->method()Ljava/lang/Integer;"
-                                )));
+                                ),
+                                FLAG));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         verify(mStatus, atLeastOnce()).error(any(), any());
@@ -139,7 +144,8 @@
                 ImmutableMap.of(ANNOTATION,
                         new CovariantReturnTypeHandler(
                                 mConsumer,
-                                emptySet()));
+                                emptySet(),
+                                FLAG));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         verify(mStatus, atLeastOnce()).error(any(), any());
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java
index 7f4ce62..1202564 100644
--- a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java
+++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -38,6 +39,7 @@
 
 public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBase {
 
+    private static final String FLAG = "test-flag";
 
     @Before
     public void setup() throws IOException {
@@ -79,16 +81,17 @@
                         new CovariantReturnTypeMultiHandler(
                                 mConsumer,
                                 ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"),
+                                FLAG,
                                 "Lannotation/Annotation;"));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> whitelist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(2)).whitelistEntry(whitelist.capture());
+        verify(mConsumer, times(2)).consume(whitelist.capture(), any(),
+                eq(ImmutableSet.of(FLAG)));
         assertThat(whitelist.getAllValues()).containsExactly(
                 "La/b/Class;->method()Ljava/lang/Integer;",
-                "La/b/Class;->method()Ljava/lang/Long;"
-        );
+                "La/b/Class;->method()Ljava/lang/Long;");
     }
 
     @Test
@@ -108,6 +111,7 @@
                         new CovariantReturnTypeMultiHandler(
                                 mConsumer,
                                 emptySet(),
+                                FLAG,
                                 "Lannotation/Annotation;"));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/FileWritingGreylistConsumerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/FileWritingGreylistConsumerTest.java
deleted file mode 100644
index 1e1b1df..0000000
--- a/tools/class2greylist/test/src/com/android/class2greylist/FileWritingGreylistConsumerTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.class2greylist;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.mockito.Mock;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-public class FileWritingGreylistConsumerTest {
-
-    @Mock
-    Status mStatus;
-    @Rule
-    public TestName mTestName = new TestName();
-    private int mFileNameSeq = 0;
-    private final List<String> mTempFiles = new ArrayList<>();
-
-    @Before
-    public void setup() throws IOException {
-        System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
-                mTestName.getMethodName()));
-        initMocks(this);
-    }
-
-    @After
-    public void removeTempFiles() {
-        for (String name : mTempFiles) {
-            new File(name).delete();
-        }
-    }
-
-    private String tempFileName() {
-        String name = String.format(Locale.US, "/tmp/test-%s-%d",
-                mTestName.getMethodName(), mFileNameSeq++);
-        mTempFiles.add(name);
-        return name;
-    }
-
-    @Test
-    public void testSimpleMap() throws FileNotFoundException {
-        Map<Integer, PrintStream> streams = FileWritingGreylistConsumer.openFiles(
-                ImmutableMap.of(1, tempFileName(), 2, tempFileName()));
-        assertThat(streams.keySet()).containsExactly(1, 2);
-        assertThat(streams.get(1)).isNotNull();
-        assertThat(streams.get(2)).isNotNull();
-        assertThat(streams.get(2)).isNotSameAs(streams.get(1));
-    }
-
-    @Test
-    public void testCommonMappings() throws FileNotFoundException {
-        String name = tempFileName();
-        Map<Integer, PrintStream> streams = FileWritingGreylistConsumer.openFiles(
-                ImmutableMap.of(1, name, 2, name));
-        assertThat(streams.keySet()).containsExactly(1, 2);
-        assertThat(streams.get(1)).isNotNull();
-        assertThat(streams.get(2)).isSameAs(streams.get(1));
-    }
-}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandlerTest.java
similarity index 87%
rename from tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java
rename to tools/class2greylist/test/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandlerTest.java
index edf2ecd..cdf01af 100644
--- a/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java
+++ b/tools/class2greylist/test/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandlerTest.java
@@ -19,11 +19,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import static java.util.Collections.emptySet;
+import static java.util.Collections.emptyMap;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
@@ -35,14 +36,23 @@
 import org.mockito.ArgumentCaptor;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
 
-public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase {
+public class UnsupportedAppUsageAnnotationHandlerTest extends AnnotationHandlerTestBase {
 
     private static final String ANNOTATION = "Lannotation/Anno;";
 
+    private static final Map<Integer, String> NULL_SDK_MAP;
+    static {
+        Map<Integer, String> map = new HashMap<>();
+        map.put(null, "flag-null");
+        NULL_SDK_MAP = Collections.unmodifiableMap(map);
+    }
+
     @Before
     public void setup() throws IOException {
         mJavac.addSource("annotation.Anno", Joiner.on('\n').join(
@@ -56,11 +66,11 @@
                 "}"));
     }
 
-    private GreylistAnnotationHandler createGreylistHandler(
-            Predicate<GreylistAnnotationHandler.GreylistMember> greylistFilter,
-            Set<Integer> validMaxTargetSdkValues) {
-        return new GreylistAnnotationHandler(
-                mStatus, mConsumer, greylistFilter, x -> validMaxTargetSdkValues.contains(x));
+    private UnsupportedAppUsageAnnotationHandler createGreylistHandler(
+            Predicate<UnsupportedAppUsageAnnotationHandler.ClassMember> greylistFilter,
+            Map<Integer, String> validMaxTargetSdkValues) {
+        return new UnsupportedAppUsageAnnotationHandler(
+                mStatus, mConsumer, greylistFilter, validMaxTargetSdkValues);
     }
 
     @Test
@@ -75,12 +85,12 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
     }
 
@@ -96,12 +106,12 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V");
     }
 
@@ -117,12 +127,12 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I");
     }
 
@@ -138,12 +148,12 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
     }
 
@@ -159,7 +169,7 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         verify(mStatus, times(1)).error(any(), any());
@@ -179,12 +189,12 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V");
     }
 
@@ -198,11 +208,11 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
-        verify(mConsumer, never()).greylistEntry(any(String.class), any(), any());
+        verify(mConsumer, never()).consume(any(String.class), any(), any());
     }
 
     @Test
@@ -217,12 +227,12 @@
         assertThat(mJavac.compile()).isTrue();
 
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
     }
 
@@ -245,14 +255,14 @@
         assertThat(mJavac.compile()).isTrue();
 
         Map<String, AnnotationHandler> handlerMap =
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
         // A bridge method is generated for the above, so we expect 2 greylist entries.
-        verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(2)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getAllValues()).containsExactly(
                 "La/b/Class;->method(Ljava/lang/Object;)V",
                 "La/b/Class;->method(Ljava/lang/String;)V");
@@ -277,14 +287,14 @@
         assertThat(mJavac.compile()).isTrue();
 
         Map<String, AnnotationHandler> handlerMap =
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
         // A bridge method is generated for the above, so we expect 2 greylist entries.
-        verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(2)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getAllValues()).containsExactly(
                 "La/b/Class;->method(Ljava/lang/Object;)V",
                 "La/b/Class;->method(Ljava/lang/String;)V");
@@ -313,7 +323,7 @@
         assertThat(mJavac.compile()).isTrue();
 
         Map<String, AnnotationHandler> handlerMap =
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Interface"), mStatus, handlerMap)
                 .visit();
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
@@ -322,7 +332,7 @@
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
         // A bridge method is generated for the above, so we expect 2 greylist entries.
-        verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(2)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getAllValues()).containsExactly(
                 "La/b/Class;->method(Ljava/lang/Object;)V",
                 "La/b/Base;->method(Ljava/lang/Object;)V");
@@ -351,18 +361,18 @@
                 "La/b/Class;->method(Ljava/lang/Object;)V");
         Map<String, AnnotationHandler> handlerMap =
                 ImmutableMap.of(ANNOTATION,
-                        new GreylistAnnotationHandler(
+                        new UnsupportedAppUsageAnnotationHandler(
                                 mStatus,
                                 mConsumer,
                                 publicApis,
-                                x -> false));
+                                NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
 
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
         // The bridge method generated for the above, is a public API so should be excluded
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
     }
 
@@ -379,12 +389,12 @@
 
         Map<String, AnnotationHandler> handlerMap =
                 ImmutableMap.of(ANNOTATION, createGreylistHandler(
-                        member -> !member.bridge, // exclude bridge methods
-                        emptySet()));
+                        member -> !member.isBridgeMethod, // exclude bridge methods
+                        NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
         assertNoErrors();
         ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any());
+        verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
         assertThat(greylist.getValue()).isEqualTo("La/b/Class;->field:I");
     }
 
@@ -400,7 +410,7 @@
         assertThat(mJavac.compile()).isTrue();
 
         Map<String, AnnotationHandler> handlerMap =
-                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
         verify(mStatus, times(1)).error(any(), any());
     }
@@ -419,12 +429,10 @@
         Map<String, AnnotationHandler> handlerMap =
                 ImmutableMap.of(ANNOTATION, createGreylistHandler(
                         x -> true,
-                        ImmutableSet.of(1)));
+                        ImmutableMap.of(1, "flag1")));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
         assertNoErrors();
-        ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class);
-        verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture(), any());
-        assertThat(maxTargetSdk.getValue()).isEqualTo(1);
+        verify(mConsumer, times(1)).consume(any(), any(), eq(ImmutableSet.of("flag1")));
     }
 
     @Test
@@ -441,12 +449,10 @@
         Map<String, AnnotationHandler> handlerMap =
                 ImmutableMap.of(ANNOTATION, createGreylistHandler(
                         x -> true,
-                        ImmutableSet.of(1)));
+                        NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
         assertNoErrors();
-        ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class);
-        verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture(), any());
-        assertThat(maxTargetSdk.getValue()).isEqualTo(null);
+        verify(mConsumer, times(1)).consume(any(), any(), eq(ImmutableSet.of("flag-null")));
     }
 
     @Test
@@ -463,7 +469,7 @@
         Map<String, AnnotationHandler> handlerMap =
                 ImmutableMap.of(ANNOTATION, createGreylistHandler(
                         x -> true,
-                        ImmutableSet.of(1)));
+                        NULL_SDK_MAP));
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
         verify(mStatus, times(1)).error(any(), any());
     }
@@ -490,12 +496,12 @@
         assertThat(mJavac.compile()).isTrue();
         new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
                 ImmutableMap.of("Lannotation/Anno2;", createGreylistHandler(x -> true,
-                        ImmutableSet.of(2)))
+                        ImmutableMap.of(2, "flag2")))
         ).visit();
 
         assertNoErrors();
         ArgumentCaptor<Map<String, String>> properties = ArgumentCaptor.forClass(Map.class);
-        verify(mConsumer, times(1)).greylistEntry(any(), any(), properties.capture());
+        verify(mConsumer, times(1)).consume(any(), properties.capture(), any());
         assertThat(properties.getValue()).containsExactly(
                 "maxTargetSdk", "2",
                 "trackingBug", "123456789");
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index 7c93a8b..3e38b97 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -73,10 +73,11 @@
   UsageError("    --output-dex=<filename>: file to write encoded dex into");
   UsageError("        input and output dex files are paired in order of appearance");
   UsageError("");
-  UsageError("    --light-greylist=<filename>:");
-  UsageError("    --dark-greylist=<filename>:");
-  UsageError("    --blacklist=<filename>:");
-  UsageError("        text files with signatures of methods/fields to be annotated");
+  UsageError("    --api-flags=<filename>:");
+  UsageError("        CSV file with signatures of methods/fields and their respective flags");
+  UsageError("");
+  UsageError("    --no-force-assign-all:");
+  UsageError("        Disable check that all dex entries have been assigned a flag");
   UsageError("");
   UsageError("  Command \"list\": dump lists of public and private API");
   UsageError("    --boot-dex=<filename>: dex file which belongs to boot class path");
@@ -857,7 +858,7 @@
 
 class HiddenApi final {
  public:
-  HiddenApi() {}
+  HiddenApi() : force_assign_all_(true) {}
 
   void Run(int argc, char** argv) {
     switch (ParseArgs(argc, argv)) {
@@ -890,12 +891,10 @@
             boot_dex_paths_.push_back(option.substr(strlen("--input-dex=")).ToString());
           } else if (option.starts_with("--output-dex=")) {
             output_dex_paths_.push_back(option.substr(strlen("--output-dex=")).ToString());
-          } else if (option.starts_with("--light-greylist=")) {
-            light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString();
-          } else if (option.starts_with("--dark-greylist=")) {
-            dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString();
-          } else if (option.starts_with("--blacklist=")) {
-            blacklist_path_ = option.substr(strlen("--blacklist=")).ToString();
+          } else if (option.starts_with("--api-flags=")) {
+            api_list_path_ = option.substr(strlen("--api-flags=")).ToString();
+          } else if (option == "--no-force-assign-all") {
+            force_assign_all_ = false;
           } else {
             Usage("Unknown argument '%s'", option.data());
           }
@@ -934,10 +933,7 @@
     }
 
     // Load dex signatures.
-    std::map<std::string, hiddenapi::ApiList> api_list;
-    OpenApiFile(light_greylist_path_, api_list, hiddenapi::ApiList::Greylist());
-    OpenApiFile(dark_greylist_path_, api_list, hiddenapi::ApiList::GreylistMaxO());
-    OpenApiFile(blacklist_path_, api_list, hiddenapi::ApiList::Blacklist());
+    std::map<std::string, hiddenapi::ApiList> api_list = OpenApiFile(api_list_path_);
 
     // Iterate over input dex files and insert HiddenapiClassData sections.
     for (size_t i = 0; i < boot_dex_paths_.size(); ++i) {
@@ -950,14 +946,18 @@
       const DexFile& input_dex = *input_dex_files[0];
 
       HiddenapiClassDataBuilder builder(input_dex);
-      boot_classpath.ForEachDexClass([&api_list, &builder](const DexClass& boot_class) {
+      boot_classpath.ForEachDexClass([&](const DexClass& boot_class) {
         builder.BeginClassDef(boot_class.GetClassDefIndex());
         if (boot_class.GetData() != nullptr) {
           auto fn_shared = [&](const DexMember& boot_member) {
-            // TODO: Load whitelist and CHECK that entry was found.
             auto it = api_list.find(boot_member.GetApiEntry());
-            builder.WriteFlags(
-                (it == api_list.end()) ? hiddenapi::ApiList::Whitelist() : it->second);
+            bool api_list_found = (it != api_list.end());
+            // TODO: Fix ART buildbots and turn this into a CHECK.
+            if (force_assign_all_ && !api_list_found) {
+              LOG(WARNING) << "Could not find hiddenapi flags for dex entry: "
+                           << boot_member.GetApiEntry();
+            }
+            builder.WriteFlags(api_list_found ? it->second : hiddenapi::ApiList::Whitelist());
           };
           auto fn_field = [&](const ClassAccessor::Field& boot_field) {
             fn_shared(DexMember(boot_class, boot_field));
@@ -976,23 +976,29 @@
     }
   }
 
-  void OpenApiFile(const std::string& path,
-                   std::map<std::string, hiddenapi::ApiList>& api_list,
-                   hiddenapi::ApiList membership) {
-    if (path.empty()) {
-      return;
-    }
-
+  std::map<std::string, hiddenapi::ApiList> OpenApiFile(const std::string& path) {
+    CHECK(!path.empty());
     std::ifstream api_file(path, std::ifstream::in);
     CHECK(!api_file.fail()) << "Unable to open file '" << path << "' " << strerror(errno);
 
+    std::map<std::string, hiddenapi::ApiList> api_flag_map;
+
     for (std::string line; std::getline(api_file, line);) {
-      CHECK(api_list.find(line) == api_list.end())
-          << "Duplicate entry: " << line << " (" << api_list.find(line)->second
-          << " and " << membership << ")";
-      api_list.emplace(line, membership);
+      std::vector<std::string> values = android::base::Split(line, ",");
+      CHECK_EQ(values.size(), 2u) << "Currently only signature and one flag are supported";
+
+      const std::string& signature = values[0];
+      CHECK(api_flag_map.find(signature) == api_flag_map.end()) << "Duplicate entry: " << signature;
+
+      const std::string& flag_str = values[1];
+      hiddenapi::ApiList membership = hiddenapi::ApiList::FromName(flag_str);
+      CHECK(membership.IsValid()) << "Unknown ApiList name: " << flag_str;
+
+      api_flag_map.emplace(signature, membership);
     }
+
     api_file.close();
+    return api_flag_map;
   }
 
   void ListApi() {
@@ -1071,6 +1077,10 @@
     file_private.close();
   }
 
+  // Whether to check that all dex entries have been assigned flags.
+  // Defaults to true.
+  bool force_assign_all_;
+
   // Paths to DEX files which should be processed.
   std::vector<std::string> boot_dex_paths_;
 
@@ -1082,9 +1092,7 @@
   std::vector<std::vector<std::string>> stub_classpaths_;
 
   // Paths to text files which contain the lists of API members.
-  std::string light_greylist_path_;
-  std::string dark_greylist_path_;
-  std::string blacklist_path_;
+  std::string api_list_path_;
 
   // Paths to text files to which we will output list of all API members.
   std::string out_public_path_;
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index 66ce2de..f10d3f4 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -41,9 +41,7 @@
     return file_path;
   }
 
-  std::unique_ptr<const DexFile> RunHiddenApi(const ScratchFile& light_greylist,
-                                              const ScratchFile& dark_greylist,
-                                              const ScratchFile& blacklist,
+  std::unique_ptr<const DexFile> RunHiddenApi(const ScratchFile& flags_csv,
                                               const std::vector<std::string>& extra_args,
                                               ScratchFile* out_dex) {
     std::string error;
@@ -71,9 +69,8 @@
     argv_str.push_back("encode");
     argv_str.push_back("--input-dex=" + in_dex.GetFilename());
     argv_str.push_back("--output-dex=" + out_dex->GetFilename());
-    argv_str.push_back("--light-greylist=" + light_greylist.GetFilename());
-    argv_str.push_back("--dark-greylist=" + dark_greylist.GetFilename());
-    argv_str.push_back("--blacklist=" + blacklist.GetFilename());
+    argv_str.push_back("--api-flags=" + flags_csv.GetFilename());
+    argv_str.push_back("--no-force-assign-all");
     int return_code = ExecAndReturnCode(argv_str, &error);
     if (return_code == 0) {
       return OpenDex(*out_dex);
@@ -218,404 +215,428 @@
 };
 
 TEST_F(HiddenApiTest, InstanceFieldNoMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:LBadType1;,greylist" << std::endl
+      << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetIFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceFieldLightGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:I,greylist" << std::endl
+      << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetIFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceFieldDarkGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:LBadType1;,greylist" << std::endl
+      << "LMain;->ifield:I,greylist-max-o" << std::endl
+      << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetIFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceFieldBlacklistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:I" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:LBadType1;,greylist" << std::endl
+      << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->ifield:I,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetIFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch1) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:I" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:LBadType1;,greylist" << std::endl
+      << "LMain;->ifield:I,blacklist,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch2) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:I" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->ifield:I,blacklist,greylist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch3) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl;
-  OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->ifield:I,greylist,greylist-max-o" << std::endl
+      << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticFieldNoMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:LBadType1;,greylist" << std::endl
+      << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetSFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticFieldLightGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:Ljava/lang/Object;,greylist" << std::endl
+      << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetSFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticFieldDarkGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:LBadType1;,greylist" << std::endl
+      << "LMain;->sfield:Ljava/lang/Object;,greylist-max-o" << std::endl
+      << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetSFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticFieldBlacklistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:LBadType1;,greylist" << std::endl
+      << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->sfield:Ljava/lang/Object;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetSFieldHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticFieldTwoListsMatch1) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:LBadType1;,greylist" << std::endl
+      << "LMain;->sfield:Ljava/lang/Object;,blacklist,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticFieldTwoListsMatch2) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
+      << "LMain;->sfield:Ljava/lang/Object;,blacklist,greylist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticFieldTwoListsMatch3) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl;
-  OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->sfield:Ljava/lang/Object;,greylist,greylist-max-o" << std::endl
+      << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceMethodNoMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetIMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceMethodLightGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(J)V,greylist" << std::endl
+      << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetIMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceMethodDarkGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->imethod(J)V,greylist-max-o" << std::endl
+      << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetIMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceMethodBlacklistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->imethod(J)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetIMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch1) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->imethod(J)V,blacklist,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch2) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->imethod(J)V,blacklist,greylist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch3) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->imethod(J)V,greylist,greylist-max-o" << std::endl
+      << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticMethodNoMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetSMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticMethodLightGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(Ljava/lang/Object;)V,greylist" << std::endl
+      << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetSMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticMethodDarkGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->smethod(Ljava/lang/Object;)V,greylist-max-o" << std::endl
+      << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetSMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticMethodBlacklistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->smethod(Ljava/lang/Object;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetSMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticMethodTwoListsMatch1) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->smethod(Ljava/lang/Object;)V,blacklist,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticMethodTwoListsMatch2) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->smethod(Ljava/lang/Object;)V,blacklist,greylist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticMethodTwoListsMatch3) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->smethod(Ljava/lang/Object;)V,greylist,greylist-max-o" << std::endl
+      << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodNoMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetINMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodLightGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(C)V,greylist" << std::endl
+      << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetINMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodDarkGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->inmethod(C)V,greylist-max-o" << std::endl
+      << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetINMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodBlacklistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->inmethod(C)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetINMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch1) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->inmethod(C)V,blacklist,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch2) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(C)V,blacklist,greylist" << std::endl
+      << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch3) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->inmethod(C)V,greylist,greylist-max-o" << std::endl
+      << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodNoMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetSNMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodLightGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(Ljava/lang/Integer;)V,greylist" << std::endl
+      << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetSNMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodDarkGreylistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->snmethod(Ljava/lang/Integer;)V,greylist-max-o" << std::endl
+      << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetSNMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodBlacklistMatch) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl
+      << "LMain;->snmethod(Ljava/lang/Integer;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetSNMethodHiddenFlags(*dex_file));
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch1) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
+      << "LMain;->snmethod(Ljava/lang/Integer;)V,blacklist,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch2) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(Ljava/lang/Integer;)V,blacklist,greylist" << std::endl
+      << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
 TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch3) {
-  ScratchFile dex, light_greylist, dark_greylist, blacklist;
-  OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl;
-  OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl;
-  auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex);
+  ScratchFile dex, flags_csv;
+  OpenStream(flags_csv)
+      << "LMain;->snmethod(Ljava/lang/Integer;)V,greylist,greylist-max-o" << std::endl
+      << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
+  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
diff --git a/tools/veridex/Android.mk b/tools/veridex/Android.mk
index c0b5ca1..c510a51 100644
--- a/tools/veridex/Android.mk
+++ b/tools/veridex/Android.mk
@@ -30,15 +30,10 @@
 $(oahl_stub_dex): $(call get-prebuilt-sdk-dir,current)/org.apache.http.legacy.jar | $(ZIP2ZIP) $(DX)
 	$(transform-classes.jar-to-dex)
 
-app_compat_lists := \
-  $(INTERNAL_PLATFORM_HIDDENAPI_WHITELIST) \
-  $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \
-  $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) \
-  $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)
-
 # Phony rule to create all dependencies of the appcompat.sh script.
 .PHONY: appcompat
-appcompat: $(system_stub_dex) $(oahl_stub_dex) $(HOST_OUT_EXECUTABLES)/veridex $(app_compat_lists)
+appcompat: $(system_stub_dex) $(oahl_stub_dex) $(HOST_OUT_EXECUTABLES)/veridex \
+    $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)
 
 VERIDEX_FILES_PATH := \
     $(call intermediates-dir-for,PACKAGING,veridex,HOST)/veridex.zip
@@ -46,12 +41,12 @@
 VERIDEX_FILES := $(LOCAL_PATH)/appcompat.sh
 
 $(VERIDEX_FILES_PATH): PRIVATE_VERIDEX_FILES := $(VERIDEX_FILES)
-$(VERIDEX_FILES_PATH): PRIVATE_APP_COMPAT_LISTS := $(app_compat_lists)
 $(VERIDEX_FILES_PATH): PRIVATE_SYSTEM_STUBS_DEX_DIR := $(dir $(system_stub_dex))
 $(VERIDEX_FILES_PATH): PRIVATE_SYSTEM_STUBS_ZIP := $(dir $(VERIDEX_FILES_PATH))/system-stubs.zip
 $(VERIDEX_FILES_PATH): PRIVATE_OAHL_STUBS_DEX_DIR := $(dir $(oahl_stub_dex))
 $(VERIDEX_FILES_PATH): PRIVATE_OAHL_STUBS_ZIP := $(dir $(VERIDEX_FILES_PATH))/org.apache.http.legacy-stubs.zip
-$(VERIDEX_FILES_PATH) : $(SOONG_ZIP) $(VERIDEX_FILES) $(app_compat_lists) $(HOST_OUT_EXECUTABLES)/veridex $(system_stub_dex) $(oahl_stub_dex)
+$(VERIDEX_FILES_PATH) : $(SOONG_ZIP) $(VERIDEX_FILES) $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS) \
+    $(HOST_OUT_EXECUTABLES)/veridex $(system_stub_dex) $(oahl_stub_dex)
 	rm -rf $(dir $@)/*
 	ls -1 $(PRIVATE_SYSTEM_STUBS_DEX_DIR)/classes*.dex | sort >$(PRIVATE_SYSTEM_STUBS_ZIP).list
 	$(SOONG_ZIP) -o $(PRIVATE_SYSTEM_STUBS_ZIP) -C $(PRIVATE_SYSTEM_STUBS_DEX_DIR) -l $(PRIVATE_SYSTEM_STUBS_ZIP).list
@@ -60,10 +55,11 @@
 	$(SOONG_ZIP) -o $(PRIVATE_OAHL_STUBS_ZIP) -C $(PRIVATE_OAHL_STUBS_DEX_DIR) -l $(PRIVATE_OAHL_STUBS_ZIP).list
 	rm $(PRIVATE_OAHL_STUBS_ZIP).list
 	$(SOONG_ZIP) -o $@ -C art/tools/veridex -f $(PRIVATE_VERIDEX_FILES) \
-                     -C $(dir $(lastword $(PRIVATE_APP_COMPAT_LISTS))) $(addprefix -f , $(PRIVATE_APP_COMPAT_LISTS)) \
-                     -C $(HOST_OUT_EXECUTABLES) -f $(HOST_OUT_EXECUTABLES)/veridex \
-                     -C $(dir $(PRIVATE_SYSTEM_STUBS_ZIP)) -f $(PRIVATE_SYSTEM_STUBS_ZIP) \
-                     -C $(dir $(PRIVATE_OAHL_STUBS_ZIP)) -f $(PRIVATE_OAHL_STUBS_ZIP)
+	                    -C $(dir $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)) \
+	                        -f $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS) \
+	                   -C $(HOST_OUT_EXECUTABLES) -f $(HOST_OUT_EXECUTABLES)/veridex \
+	                   -C $(dir $(PRIVATE_SYSTEM_STUBS_ZIP)) -f $(PRIVATE_SYSTEM_STUBS_ZIP) \
+	                   -C $(dir $(PRIVATE_OAHL_STUBS_ZIP)) -f $(PRIVATE_OAHL_STUBS_ZIP)
 	rm -f $(PRIVATE_SYSTEM_STUBS_ZIP)
 	rm -f $(PRIVATE_OAHL_STUBS_ZIP)
 
@@ -71,6 +67,5 @@
 $(call dist-for-goals,sdk,$(VERIDEX_FILES_PATH))
 
 VERIDEX_FILES :=
-app_compat_lists :=
 system_stub_dex :=
 oahl_stub_dex :=
diff --git a/tools/veridex/appcompat.sh b/tools/veridex/appcompat.sh
index f57c8a4..46d62db 100755
--- a/tools/veridex/appcompat.sh
+++ b/tools/veridex/appcompat.sh
@@ -22,18 +22,12 @@
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 
 if [[ -e ${SCRIPT_DIR}/veridex && \
-      -e ${SCRIPT_DIR}/hiddenapi-whitelist.txt && \
-      -e ${SCRIPT_DIR}/hiddenapi-blacklist.txt && \
-      -e ${SCRIPT_DIR}/hiddenapi-light-greylist.txt && \
-      -e ${SCRIPT_DIR}/hiddenapi-dark-greylist.txt && \
+      -e ${SCRIPT_DIR}/hiddenapi-flags.csv && \
       -e ${SCRIPT_DIR}/org.apache.http.legacy-stubs.zip && \
       -e ${SCRIPT_DIR}/system-stubs.zip ]]; then
   exec ${SCRIPT_DIR}/veridex \
     --core-stubs=${SCRIPT_DIR}/system-stubs.zip:${SCRIPT_DIR}/org.apache.http.legacy-stubs.zip \
-    --whitelist=${SCRIPT_DIR}/hiddenapi-whitelist.txt \
-    --blacklist=${SCRIPT_DIR}/hiddenapi-blacklist.txt \
-    --light-greylist=${SCRIPT_DIR}/hiddenapi-light-greylist.txt \
-    --dark-greylist=${SCRIPT_DIR}/hiddenapi-dark-greylist.txt \
+    --api-flags=${SCRIPT_DIR}/hiddenapi-flags.csv \
     $@
 fi
 
@@ -66,8 +60,5 @@
 
 ${ANDROID_HOST_OUT}/bin/veridex \
     --core-stubs=${PACKAGING}/core_dex_intermediates/classes.dex:${PACKAGING}/oahl_dex_intermediates/classes.dex \
-    --whitelist=${PACKAGING}/hiddenapi-whitelist.txt \
-    --blacklist=${PACKAGING}/hiddenapi-blacklist.txt \
-    --light-greylist=${PACKAGING}/hiddenapi-light-greylist.txt \
-    --dark-greylist=${PACKAGING}/hiddenapi-dark-greylist.txt \
+    --api-flags=${PACKAGING}/hiddenapi-flags.csv \
     $@
diff --git a/tools/veridex/hidden_api.cc b/tools/veridex/hidden_api.cc
index 17fa1b8..6a04365 100644
--- a/tools/veridex/hidden_api.cc
+++ b/tools/veridex/hidden_api.cc
@@ -19,10 +19,63 @@
 #include <fstream>
 #include <sstream>
 
+#include "android-base/strings.h"
 #include "dex/dex_file-inl.h"
 
 namespace art {
 
+HiddenApi::HiddenApi(const char* filename, bool sdk_uses_only) {
+  CHECK(filename != nullptr);
+
+  std::ifstream in(filename);
+  for (std::string str; std::getline(in, str);) {
+    std::vector<std::string> values = android::base::Split(str, ",");
+    CHECK_EQ(values.size(), 2u) << "Currently only signature and one flag are supported";
+
+    const std::string& signature = values[0];
+    const std::string& flag_str = values[1];
+
+    hiddenapi::ApiList membership = hiddenapi::ApiList::FromName(flag_str);
+    CHECK(membership.IsValid()) << "Unknown ApiList name: " << flag_str;
+
+    if (sdk_uses_only != (membership == hiddenapi::ApiList::Whitelist())) {
+      // Either we want only SDK uses and this is not a whitelist entry,
+      // or we want only non-SDK uses and this is a whitelist entry.
+      continue;
+    }
+
+    AddSignatureToApiList(signature, membership);
+    size_t pos = signature.find("->");
+    if (pos != std::string::npos) {
+      // Add the class name.
+      AddSignatureToApiList(signature.substr(0, pos), membership);
+      pos = signature.find('(');
+      if (pos != std::string::npos) {
+        // Add the class->method name (so stripping the signature).
+        AddSignatureToApiList(signature.substr(0, pos), membership);
+      }
+      pos = signature.find(':');
+      if (pos != std::string::npos) {
+        // Add the class->field name (so stripping the type).
+        AddSignatureToApiList(signature.substr(0, pos), membership);
+      }
+    }
+  }
+}
+
+void HiddenApi::AddSignatureToApiList(const std::string& signature, hiddenapi::ApiList membership) {
+  auto it = api_list_.find(signature);
+  if (it == api_list_.end()) {
+    // Does not exist yet. Add it to list.
+    api_list_.emplace(signature, membership);
+  } else if (membership.GetMaxAllowedSdkVersion() < it->second.GetMaxAllowedSdkVersion()) {
+    // Already exist but `membership` is more restrictive.
+    it->second = membership;
+  } else {
+    // Already exists and `membership` is equally or less restrictive.
+  }
+}
+
 std::string HiddenApi::GetApiMethodName(const DexFile& dex_file, uint32_t method_index) {
   std::stringstream ss;
   const DexFile::MethodId& method_id = dex_file.GetMethodId(method_index);
@@ -44,30 +97,4 @@
   return ss.str();
 }
 
-void HiddenApi::FillList(const char* filename, std::set<std::string>& entries) {
-  if (filename == nullptr) {
-    return;
-  }
-  std::ifstream in(filename);
-  std::string str;
-  while (std::getline(in, str)) {
-    entries.insert(str);
-    size_t pos = str.find("->");
-    if (pos != std::string::npos) {
-      // Add the class name.
-      entries.insert(str.substr(0, pos));
-      pos = str.find('(');
-      if (pos != std::string::npos) {
-        // Add the class->method name (so stripping the signature).
-        entries.insert(str.substr(0, pos));
-      }
-      pos = str.find(':');
-      if (pos != std::string::npos) {
-        // Add the class->field name (so stripping the type).
-        entries.insert(str.substr(0, pos));
-      }
-    }
-  }
-}
-
 }  // namespace art
diff --git a/tools/veridex/hidden_api.h b/tools/veridex/hidden_api.h
index b58332b..d6a6438 100644
--- a/tools/veridex/hidden_api.h
+++ b/tools/veridex/hidden_api.h
@@ -20,8 +20,8 @@
 #include "base/hiddenapi_flags.h"
 #include "dex/method_reference.h"
 
+#include <map>
 #include <ostream>
-#include <set>
 #include <string>
 
 namespace art {
@@ -33,28 +33,11 @@
  */
 class HiddenApi {
  public:
-  HiddenApi(const char* whitelist,
-            const char* blacklist,
-            const char* dark_greylist,
-            const char* light_greylist) {
-    FillList(light_greylist, light_greylist_);
-    FillList(dark_greylist, dark_greylist_);
-    FillList(blacklist, blacklist_);
-    FillList(whitelist, whitelist_);
-  }
+  HiddenApi(const char* flags_file, bool sdk_uses_only);
 
   hiddenapi::ApiList GetApiList(const std::string& name) const {
-    if (IsInList(name, blacklist_)) {
-      return hiddenapi::ApiList::Blacklist();
-    } else if (IsInList(name, dark_greylist_)) {
-      return hiddenapi::ApiList::GreylistMaxO();
-    } else if (IsInList(name, light_greylist_)) {
-      return hiddenapi::ApiList::Greylist();
-    } else if (IsInList(name, whitelist_)) {
-      return hiddenapi::ApiList::Whitelist();
-    } else {
-      return hiddenapi::ApiList::Invalid();
-    }
+    auto it = api_list_.find(name);
+    return (it == api_list_.end()) ? hiddenapi::ApiList::Invalid() : it->second;
   }
 
   bool IsInAnyList(const std::string& name) const {
@@ -76,16 +59,9 @@
   }
 
  private:
-  static bool IsInList(const std::string& name, const std::set<std::string>& list) {
-    return list.find(name) != list.end();
-  }
+  void AddSignatureToApiList(const std::string& signature, hiddenapi::ApiList membership);
 
-  static void FillList(const char* filename, std::set<std::string>& entries);
-
-  std::set<std::string> whitelist_;
-  std::set<std::string> blacklist_;
-  std::set<std::string> light_greylist_;
-  std::set<std::string> dark_greylist_;
+  std::map<std::string, hiddenapi::ApiList> api_list_;
 };
 
 struct HiddenApiStats {
diff --git a/tools/veridex/veridex.cc b/tools/veridex/veridex.cc
index 2787950..96100b2 100644
--- a/tools/veridex/veridex.cc
+++ b/tools/veridex/veridex.cc
@@ -67,10 +67,7 @@
 
 static const char* kDexFileOption = "--dex-file=";
 static const char* kStubsOption = "--core-stubs=";
-static const char* kWhitelistOption = "--whitelist=";
-static const char* kBlacklistOption = "--blacklist=";
-static const char* kDarkGreylistOption = "--dark-greylist=";
-static const char* kLightGreylistOption = "--light-greylist=";
+static const char* kFlagsOption = "--api-flags=";
 static const char* kImprecise = "--imprecise";
 static const char* kTargetSdkVersion = "--target-sdk-version=";
 static const char* kOnlyReportSdkUses = "--only-report-sdk-uses";
@@ -78,10 +75,7 @@
 struct VeridexOptions {
   const char* dex_file = nullptr;
   const char* core_stubs = nullptr;
-  const char* whitelist = nullptr;
-  const char* blacklist = nullptr;
-  const char* light_greylist = nullptr;
-  const char* dark_greylist = nullptr;
+  const char* flags_file = nullptr;
   bool precise = true;
   int target_sdk_version = 28; /* P */
   bool only_report_sdk_uses = false;
@@ -105,14 +99,8 @@
       options->dex_file = Substr(argv[i], strlen(kDexFileOption));
     } else if (StartsWith(argv[i], kStubsOption)) {
       options->core_stubs = Substr(argv[i], strlen(kStubsOption));
-    } else if (StartsWith(argv[i], kWhitelistOption)) {
-      options->whitelist = Substr(argv[i], strlen(kWhitelistOption));
-    } else if (StartsWith(argv[i], kBlacklistOption)) {
-      options->blacklist = Substr(argv[i], strlen(kBlacklistOption));
-    } else if (StartsWith(argv[i], kDarkGreylistOption)) {
-      options->dark_greylist = Substr(argv[i], strlen(kDarkGreylistOption));
-    } else if (StartsWith(argv[i], kLightGreylistOption)) {
-      options->light_greylist = Substr(argv[i], strlen(kLightGreylistOption));
+    } else if (StartsWith(argv[i], kFlagsOption)) {
+      options->flags_file = Substr(argv[i], strlen(kFlagsOption));
     } else if (strcmp(argv[i], kImprecise) == 0) {
       options->precise = false;
     } else if (StartsWith(argv[i], kTargetSdkVersion)) {
@@ -229,20 +217,8 @@
     std::vector<std::unique_ptr<VeridexResolver>> app_resolvers;
     Resolve(app_dex_files, resolver_map, type_map, &app_resolvers);
 
-    if (options.only_report_sdk_uses) {
-      // If we only need to report SDK uses, clear out any of the other lists so that
-      // the analysis don't report them.
-      options.blacklist = nullptr;
-      options.dark_greylist = nullptr;
-      options.light_greylist = nullptr;
-    } else {
-      // Otherwise, omit SDK uses.
-      options.whitelist = nullptr;
-    }
-
     // Find and log uses of hidden APIs.
-    HiddenApi hidden_api(
-        options.whitelist, options.blacklist, options.dark_greylist, options.light_greylist);
+    HiddenApi hidden_api(options.flags_file, options.only_report_sdk_uses);
     HiddenApiStats stats;
 
     HiddenApiFinder api_finder(hidden_api);
@@ -277,12 +253,12 @@
       os << stats.count << " hidden API(s) used: "
          << stats.linking_count << " linked against, "
          << stats.reflection_count << " through reflection" << std::endl;
-      os << kPrefix << stats.api_counts[hiddenapi::ApiList::Blacklist().GetIntValue()]
-         << " in blacklist" << std::endl;
-      os << kPrefix << stats.api_counts[hiddenapi::ApiList::GreylistMaxO().GetIntValue()]
-         << " in dark greylist" << std::endl;
-      os << kPrefix << stats.api_counts[hiddenapi::ApiList::Greylist().GetIntValue()]
-         << " in light greylist" << std::endl;
+      for (size_t i = 0; i < hiddenapi::ApiList::kValueCount; ++i) {
+        hiddenapi::ApiList api_list = hiddenapi::ApiList::FromIntValue(i);
+        if (api_list != hiddenapi::ApiList::Whitelist()) {
+          os << kPrefix << stats.api_counts[i] << " in " << api_list << std::endl;
+        }
+      }
     }
   }