Merge "No need to JAR-sign OTA update packages."
diff --git a/core/Makefile b/core/Makefile
index b3b456e..83a008c 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1678,11 +1678,12 @@
 
 ifeq ($(AB_OTA_UPDATER),true)
 # Build zlib fingerprint if using the AB Updater.
-$(BUILT_TARGET_FILES_PACKAGE): $(TARGET_OUT_COMMON_GEN)/zlib_fingerprint
+updater_dep := $(TARGET_OUT_COMMON_GEN)/zlib_fingerprint
 else
 # Build OTA tools if not using the AB Updater.
-$(BUILT_TARGET_FILES_PACKAGE): $(built_ota_tools)
+updater_dep := $(built_ota_tools)
 endif
+$(BUILT_TARGET_FILES_PACKAGE): $(updater_dep)
 
 # If we are using recovery as boot, output recovery files to BOOT/.
 ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
@@ -1705,6 +1706,7 @@
 		$(SELINUX_FC) \
 		$(APKCERTS_FILE) \
 		$(HOST_OUT_EXECUTABLES)/fs_config \
+		build/tools/releasetools/add_img_to_target_files \
 		| $(ACP)
 	@echo "Package target files: $@"
 	$(hide) rm -rf $@ $(zip_root)
@@ -1934,7 +1936,8 @@
 
 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
 
-$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE)
+$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) \
+		build/tools/releasetools/ota_from_target_files
 	@echo "Package OTA: $@"
 	$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
 	   ./build/tools/releasetools/ota_from_target_files -v \
@@ -1960,7 +1963,8 @@
 
 INTERNAL_UPDATE_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
 
-$(INTERNAL_UPDATE_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE)
+$(INTERNAL_UPDATE_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) \
+		build/tools/releasetools/img_from_target_files
 	@echo "Package: $@"
 	$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
 	   ./build/tools/releasetools/img_from_target_files -v \
@@ -1983,7 +1987,11 @@
 SYMBOLS_ZIP := $(PRODUCT_OUT)/$(name).zip
 # For apps_only build we'll establish the dependency later in build/core/main.mk.
 ifndef TARGET_BUILD_APPS
-$(SYMBOLS_ZIP): $(INSTALLED_SYSTEMIMAGE) $(INSTALLED_BOOTIMAGE_TARGET)
+$(SYMBOLS_ZIP): $(INSTALLED_SYSTEMIMAGE) \
+		$(INSTALLED_BOOTIMAGE_TARGET) \
+		$(INSTALLED_USERDATAIMAGE_TARGET) \
+		$(INSTALLED_VENDORIMAGE_TARGET) \
+		$(updater_dep)
 endif
 $(SYMBOLS_ZIP):
 	@echo "Package symbols: $@"
diff --git a/core/definitions.mk b/core/definitions.mk
index 420691d..57aa7b6 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -144,7 +144,10 @@
 define filter-soong-makefiles
 $(foreach mk,$(1),\
   $(if $(wildcard $(patsubst %/Android.mk,%/Android.bp,$(mk))),\
-    $(info skipping $(mk) ...),\
+    $(if $(wildcard $(patsubst %/Android.mk,%/Android.soong.mk,$(mk))),\
+      $(info skipping $(mk), but including Android.soong.mk ...)\
+        $(patsubst %/Android.mk,%/Android.soong.mk,$(mk)),\
+      $(info skipping $(mk) ...)),\
     $(mk)))
 endef
 else
@@ -402,7 +405,7 @@
 
 define find-subdir-assets
 $(sort $(if $(1),$(patsubst ./%,%, \
-	$(shell if [ -d $(1) ] ; then cd $(1) ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi)), \
+	$(shell if [ -d $(1) ] ; then cd $(1) ; find -L ./ -not -name '.*' -and -type f ; fi)), \
 	$(warning Empty argument supplied to find-subdir-assets) \
 ))
 endef
diff --git a/core/host_test_internal.mk b/core/host_test_internal.mk
index e4fa4c5..6c52e64 100644
--- a/core/host_test_internal.mk
+++ b/core/host_test_internal.mk
@@ -5,7 +5,7 @@
 LOCAL_CFLAGS_windows += -DGTEST_OS_WINDOWS
 LOCAL_CFLAGS_linux += -DGTEST_OS_LINUX
 LOCAL_LDLIBS_linux += -lpthread
-LOCAL_CFLAGS_darwin += -DGTEST_OS_LINUX
+LOCAL_CFLAGS_darwin += -DGTEST_OS_MAC
 LOCAL_LDLIBS_darwin += -lpthread
 
 LOCAL_CFLAGS += -DGTEST_HAS_STD_STRING -O0 -g
diff --git a/core/main.mk b/core/main.mk
index c455320..3309981 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -97,6 +97,9 @@
 # and host information.
 include $(BUILD_SYSTEM)/config.mk
 
+# Default soong to on
+USE_SOONG ?= true
+
 ifndef KATI
 ifdef USE_NINJA
 $(warning USE_NINJA is ignored. Ninja is always used.)
@@ -107,6 +110,8 @@
 include build/core/ninja.mk
 else # KATI
 
+include $(SOONG_MAKEVARS_MK)
+
 # Write the build number to a file so it can be read back in
 # without changing the command line every time.  Avoids rebuilds
 # when using ninja.
diff --git a/core/ninja.mk b/core/ninja.mk
index 9e78c46..5136f4e 100644
--- a/core/ninja.mk
+++ b/core/ninja.mk
@@ -159,7 +159,7 @@
 endif
 $(KATI_BUILD_NINJA): $(CKATI) $(MAKEPARALLEL) $(DUMMY_OUT_MKS) run_soong FORCE
 	@echo Running kati to generate build$(KATI_NINJA_SUFFIX).ninja...
-	+$(hide) $(KATI_MAKEPARALLEL) $(CKATI) --ninja --ninja_dir=$(OUT_DIR) --ninja_suffix=$(KATI_NINJA_SUFFIX) --regen --ignore_dirty=$(OUT_DIR)/% --no_ignore_dirty=$(SOONG_ANDROID_MK) --ignore_optional_include=$(OUT_DIR)/%.P --detect_android_echo $(KATI_FIND_EMULATOR) -f build/core/main.mk $(KATI_GOALS) --gen_all_targets BUILDING_WITH_NINJA=true SOONG_ANDROID_MK=$(SOONG_ANDROID_MK)
+	+$(hide) $(KATI_MAKEPARALLEL) $(CKATI) --ninja --ninja_dir=$(OUT_DIR) --ninja_suffix=$(KATI_NINJA_SUFFIX) --regen --ignore_dirty=$(OUT_DIR)/% --no_ignore_dirty=$(SOONG_ANDROID_MK) --no_ignore_dirty=$(SOONG_MAKEVARS_MK) --ignore_optional_include=$(OUT_DIR)/%.P --detect_android_echo $(KATI_FIND_EMULATOR) -f build/core/main.mk $(KATI_GOALS) --gen_all_targets BUILDING_WITH_NINJA=true SOONG_ANDROID_MK=$(SOONG_ANDROID_MK) SOONG_MAKEVARS_MK=$(SOONG_MAKEVARS_MK)
 
 .PHONY: FORCE
 FORCE:
diff --git a/core/soong.mk b/core/soong.mk
index 990a861..ebcccd3 100644
--- a/core/soong.mk
+++ b/core/soong.mk
@@ -3,12 +3,13 @@
 SOONG_BOOTSTRAP := $(SOONG_OUT_DIR)/.soong.bootstrap
 SOONG_BUILD_NINJA := $(SOONG_OUT_DIR)/build.ninja
 SOONG_IN_MAKE := $(SOONG_OUT_DIR)/.soong.in_make
+SOONG_MAKEVARS_MK := $(SOONG_OUT_DIR)/make_vars-$(TARGET_PRODUCT).mk
 SOONG_VARIABLES := $(SOONG_OUT_DIR)/soong.variables
 
 # Only include the Soong-generated Android.mk if we're merging the
 # Soong-defined binaries with Kati-defined binaries.
 ifeq ($(USE_SOONG),true)
-SOONG_ANDROID_MK := $(SOONG_OUT_DIR)/Android.mk
+SOONG_ANDROID_MK := $(SOONG_OUT_DIR)/Android-$(TARGET_PRODUCT).mk
 endif
 
 # We need to rebootstrap soong if SOONG_OUT_DIR or the reverse path from
@@ -37,6 +38,8 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) (\
 	echo '{'; \
+	echo '    "Make_suffix": "-$(TARGET_PRODUCT)",'; \
+	echo ''; \
 	echo '    "Platform_sdk_version": $(PLATFORM_SDK_VERSION),'; \
 	echo '    "Unbundled_build": $(if $(TARGET_BUILD_APPS),true,false),'; \
 	echo '    "Brillo": $(if $(BRILLO),true,false),'; \
@@ -62,7 +65,8 @@
 	echo ''; \
 	echo '    "CrossHost": "$(HOST_CROSS_OS)",'; \
 	echo '    "CrossHostArch": "$(HOST_CROSS_ARCH)",'; \
-	echo '    "CrossHostSecondaryArch": "$(HOST_CROSS_2ND_ARCH)"'; \
+	echo '    "CrossHostSecondaryArch": "$(HOST_CROSS_2ND_ARCH)",'; \
+	echo '    "Safestack": $(if $(filter true,$(USE_SAFESTACK)),true,false)'; \
 	echo '}') > $(SOONG_VARIABLES_TMP); \
 	if ! cmp -s $(SOONG_VARIABLES_TMP) $(SOONG_VARIABLES); then \
 	  mv $(SOONG_VARIABLES_TMP) $(SOONG_VARIABLES); \
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
new file mode 100644
index 0000000..30d4011
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
@@ -0,0 +1,870 @@
+/*
+ * Copyright (C) 2016 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.apksigner.core;
+
+import com.android.apksigner.core.internal.apk.v1.DigestAlgorithm;
+import com.android.apksigner.core.internal.apk.v1.V1SchemeSigner;
+import com.android.apksigner.core.internal.apk.v2.MessageDigestSink;
+import com.android.apksigner.core.internal.apk.v2.V2SchemeSigner;
+import com.android.apksigner.core.internal.util.ByteArrayOutputStreamSink;
+import com.android.apksigner.core.internal.util.Pair;
+import com.android.apksigner.core.util.DataSink;
+import com.android.apksigner.core.util.DataSource;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link ApkSignerEngine}.
+ *
+ * <p>Use {@link Builder} to obtain instances of this engine.
+ */
+public class DefaultApkSignerEngine implements ApkSignerEngine {
+
+    // IMPLEMENTATION NOTE: This engine generates a signed APK as follows:
+    // 1. The engine asks its client to output input JAR entries which are not part of JAR
+    //    signature.
+    // 2. If JAR signing (v1 signing) is enabled, the engine inspects the output JAR entries to
+    //    compute their digests, to be placed into output META-INF/MANIFEST.MF. It also inspects
+    //    the contents of input and output META-INF/MANIFEST.MF to borrow the main section of the
+    //    file. It does not care about individual (i.e., JAR entry-specific) sections. It then
+    //    emits the v1 signature (a set of JAR entries) and asks the client to output them.
+    // 3. If APK Signature Scheme v2 (v2 signing) is enabled, the engine emits an APK Signing Block
+    //    from outputZipSections() and asks its client to insert this block into the output.
+
+    private final boolean mV1SigningEnabled;
+    private final boolean mV2SigningEnabled;
+    private final boolean mOtherSignersSignaturesPreserved;
+    private final List<V1SchemeSigner.SignerConfig> mV1SignerConfigs;
+    private final DigestAlgorithm mV1ContentDigestAlgorithm;
+    private final List<V2SchemeSigner.SignerConfig> mV2SignerConfigs;
+
+    private boolean mClosed;
+
+    private boolean mV1SignaturePending;
+
+    /**
+     * Names of JAR entries which this engine is expected to output as part of v1 signing.
+     */
+    private final Set<String> mSignatureExpectedOutputJarEntryNames;
+
+    /** Requests for digests of output JAR entries. */
+    private final Map<String, GetJarEntryDataDigestRequest> mOutputJarEntryDigestRequests =
+            new HashMap<>();
+
+    /** Digests of output JAR entries. */
+    private final Map<String, byte[]> mOutputJarEntryDigests = new HashMap<>();
+
+    /** Data of JAR entries emitted by this engine as v1 signature. */
+    private final Map<String, byte[]> mEmittedSignatureJarEntryData = new HashMap<>();
+
+    /** Requests for data of output JAR entries which comprise the v1 signature. */
+    private final Map<String, GetJarEntryDataRequest> mOutputSignatureJarEntryDataRequests =
+            new HashMap<>();
+    /**
+     * Request to obtain the data of MANIFEST.MF or {@code null} if the request hasn't been issued.
+     */
+    private GetJarEntryDataRequest mInputJarManifestEntryDataRequest;
+
+    /**
+     * Request to output the emitted v1 signature or {@code null} if the request hasn't been issued.
+     */
+    private OutputJarSignatureRequestImpl mAddV1SignatureRequest;
+
+    private boolean mV2SignaturePending;
+
+    /**
+     * Request to output the emitted v2 signature or {@code null} if the request hasn't been issued.
+     */
+    private OutputApkSigningBlockRequestImpl mAddV2SignatureRequest;
+
+    private DefaultApkSignerEngine(
+            List<SignerConfig> signerConfigs,
+            int minSdkVersion,
+            boolean v1SigningEnabled,
+            boolean v2SigningEnabled,
+            boolean otherSignersSignaturesPreserved) throws InvalidKeyException {
+        if (signerConfigs.isEmpty()) {
+            throw new IllegalArgumentException("At least one signer config must be provided");
+        }
+        if (otherSignersSignaturesPreserved) {
+            throw new UnsupportedOperationException(
+                    "Preserving other signer's signatures is not yet implemented");
+        }
+
+        mV1SigningEnabled = v1SigningEnabled;
+        mV2SigningEnabled = v2SigningEnabled;
+        mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved;
+        mV1SignerConfigs =
+                (v1SigningEnabled)
+                        ? new ArrayList<>(signerConfigs.size()) : Collections.emptyList();
+        mV2SignerConfigs =
+                (v2SigningEnabled)
+                        ? new ArrayList<>(signerConfigs.size()) : Collections.emptyList();
+        mV1ContentDigestAlgorithm =
+                (v1SigningEnabled)
+                        ? V1SchemeSigner.getSuggestedContentDigestAlgorithm(minSdkVersion) : null;
+        for (SignerConfig signerConfig : signerConfigs) {
+            List<X509Certificate> certificates = signerConfig.getCertificates();
+            PublicKey publicKey = certificates.get(0).getPublicKey();
+
+            if (v1SigningEnabled) {
+                DigestAlgorithm v1SignatureDigestAlgorithm =
+                        V1SchemeSigner.getSuggestedSignatureDigestAlgorithm(
+                                publicKey, minSdkVersion);
+                V1SchemeSigner.SignerConfig v1SignerConfig = new V1SchemeSigner.SignerConfig();
+                v1SignerConfig.name = signerConfig.getName();
+                v1SignerConfig.privateKey = signerConfig.getPrivateKey();
+                v1SignerConfig.certificates = certificates;
+                v1SignerConfig.contentDigestAlgorithm = mV1ContentDigestAlgorithm;
+                v1SignerConfig.signatureDigestAlgorithm = v1SignatureDigestAlgorithm;
+                mV1SignerConfigs.add(v1SignerConfig);
+            }
+
+            if (v2SigningEnabled) {
+                V2SchemeSigner.SignerConfig v2SignerConfig = new V2SchemeSigner.SignerConfig();
+                v2SignerConfig.privateKey = signerConfig.getPrivateKey();
+                v2SignerConfig.certificates = certificates;
+                v2SignerConfig.signatureAlgorithms =
+                        V2SchemeSigner.getSuggestedSignatureAlgorithms(publicKey, minSdkVersion);
+                mV2SignerConfigs.add(v2SignerConfig);
+            }
+        }
+        mSignatureExpectedOutputJarEntryNames =
+                (v1SigningEnabled)
+                        ? V1SchemeSigner.getOutputEntryNames(mV1SignerConfigs)
+                        : Collections.emptySet();
+    }
+
+    @Override
+    public void inputApkSigningBlock(DataSource apkSigningBlock) {
+        checkNotClosed();
+
+        if ((apkSigningBlock == null) || (apkSigningBlock.size() == 0)) {
+            return;
+        }
+
+        if (mOtherSignersSignaturesPreserved) {
+            // TODO: Preserve blocks other than APK Signature Scheme v2 blocks of signers configured
+            // in this engine.
+            return;
+        }
+        // TODO: Preserve blocks other than APK Signature Scheme v2 blocks.
+    }
+
+    @Override
+    public InputJarEntryInstructions inputJarEntry(String entryName) {
+        checkNotClosed();
+
+        InputJarEntryInstructions.OutputPolicy outputPolicy =
+                getInputJarEntryOutputPolicy(entryName);
+        switch (outputPolicy) {
+            case SKIP:
+                return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.SKIP);
+            case OUTPUT:
+                return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.OUTPUT);
+            case OUTPUT_BY_ENGINE:
+                if (V1SchemeSigner.MANIFEST_ENTRY_NAME.equals(entryName)) {
+                    // We copy the main section of the JAR manifest from input to output. Thus, this
+                    // invalidates v1 signature and we need to see the entry's data.
+                    mInputJarManifestEntryDataRequest = new GetJarEntryDataRequest(entryName);
+                    return new InputJarEntryInstructions(
+                            InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE,
+                            mInputJarManifestEntryDataRequest);
+                }
+                return new InputJarEntryInstructions(
+                        InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE);
+            default:
+                throw new RuntimeException("Unsupported output policy: " + outputPolicy);
+        }
+    }
+
+    @Override
+    public InspectJarEntryRequest outputJarEntry(String entryName) {
+        checkNotClosed();
+        invalidateV2Signature();
+        if (!mV1SigningEnabled) {
+            // No need to inspect JAR entries when v1 signing is not enabled.
+            return null;
+        }
+        // v1 signing is enabled
+
+        if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) {
+            // This entry is covered by v1 signature. We thus need to inspect the entry's data to
+            // compute its digest(s) for v1 signature.
+
+            // TODO: Handle the case where other signer's v1 signatures are present and need to be
+            // preserved. In that scenario we can't modify MANIFEST.MF and add/remove JAR entries
+            // covered by v1 signature.
+            invalidateV1Signature();
+            GetJarEntryDataDigestRequest dataDigestRequest =
+                    new GetJarEntryDataDigestRequest(
+                            entryName,
+                            V1SchemeSigner.getMessageDigestInstance(mV1ContentDigestAlgorithm));
+            mOutputJarEntryDigestRequests.put(entryName, dataDigestRequest);
+            mOutputJarEntryDigests.remove(entryName);
+            return dataDigestRequest;
+        }
+
+        if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) {
+            // This entry is part of v1 signature generated by this engine. We need to check whether
+            // the entry's data is as output by the engine.
+            invalidateV1Signature();
+            GetJarEntryDataRequest dataRequest;
+            if (V1SchemeSigner.MANIFEST_ENTRY_NAME.equals(entryName)) {
+                dataRequest = new GetJarEntryDataRequest(entryName);
+                mInputJarManifestEntryDataRequest = dataRequest;
+            } else {
+                // If this entry is part of v1 signature which has been emitted by this engine,
+                // check whether the output entry's data matches what the engine emitted.
+                dataRequest =
+                        (mEmittedSignatureJarEntryData.containsKey(entryName))
+                                ? new GetJarEntryDataRequest(entryName) : null;
+            }
+
+            if (dataRequest != null) {
+                mOutputSignatureJarEntryDataRequests.put(entryName, dataRequest);
+            }
+            return dataRequest;
+        }
+
+        // This entry is not covered by v1 signature and isn't part of v1 signature.
+        return null;
+    }
+
+    @Override
+    public InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) {
+        checkNotClosed();
+        return getInputJarEntryOutputPolicy(entryName);
+    }
+
+    @Override
+    public void outputJarEntryRemoved(String entryName) {
+        checkNotClosed();
+        invalidateV2Signature();
+        if (!mV1SigningEnabled) {
+            return;
+        }
+
+        if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) {
+            // This entry is covered by v1 signature.
+            invalidateV1Signature();
+            mOutputJarEntryDigests.remove(entryName);
+            mOutputJarEntryDigestRequests.remove(entryName);
+            mOutputSignatureJarEntryDataRequests.remove(entryName);
+            return;
+        }
+
+        if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) {
+            // This entry is part of the v1 signature generated by this engine.
+            invalidateV1Signature();
+            return;
+        }
+    }
+
+    @Override
+    public OutputJarSignatureRequest outputJarEntries()
+            throws InvalidKeyException, SignatureException {
+        checkNotClosed();
+
+        if (!mV1SignaturePending) {
+            return null;
+        }
+
+        if ((mInputJarManifestEntryDataRequest != null)
+                && (!mInputJarManifestEntryDataRequest.isDone())) {
+            throw new IllegalStateException(
+                    "Still waiting to inspect input APK's "
+                            + mInputJarManifestEntryDataRequest.getEntryName());
+        }
+
+        for (GetJarEntryDataDigestRequest digestRequest
+                : mOutputJarEntryDigestRequests.values()) {
+            String entryName = digestRequest.getEntryName();
+            if (!digestRequest.isDone()) {
+                throw new IllegalStateException(
+                        "Still waiting to inspect output APK's " + entryName);
+            }
+            mOutputJarEntryDigests.put(entryName, digestRequest.getDigest());
+        }
+        mOutputJarEntryDigestRequests.clear();
+
+        for (GetJarEntryDataRequest dataRequest : mOutputSignatureJarEntryDataRequests.values()) {
+            if (!dataRequest.isDone()) {
+                throw new IllegalStateException(
+                        "Still waiting to inspect output APK's " + dataRequest.getEntryName());
+            }
+        }
+
+        List<Integer> apkSigningSchemeIds =
+                (mV2SigningEnabled) ? Collections.singletonList(2) : Collections.emptyList();
+        byte[] inputJarManifest =
+                (mInputJarManifestEntryDataRequest != null)
+                    ? mInputJarManifestEntryDataRequest.getData() : null;
+
+        // Check whether the most recently used signature (if present) is still fine.
+        List<Pair<String, byte[]>> signatureZipEntries;
+        if ((mAddV1SignatureRequest == null) || (!mAddV1SignatureRequest.isDone())) {
+            try {
+                signatureZipEntries =
+                        V1SchemeSigner.sign(
+                                mV1SignerConfigs,
+                                mV1ContentDigestAlgorithm,
+                                mOutputJarEntryDigests,
+                                apkSigningSchemeIds,
+                                inputJarManifest);
+            } catch (CertificateEncodingException e) {
+                throw new SignatureException("Failed to generate v1 signature", e);
+            }
+        } else {
+            V1SchemeSigner.OutputManifestFile newManifest =
+                    V1SchemeSigner.generateManifestFile(
+                            mV1ContentDigestAlgorithm, mOutputJarEntryDigests, inputJarManifest);
+            byte[] emittedSignatureManifest =
+                    mEmittedSignatureJarEntryData.get(V1SchemeSigner.MANIFEST_ENTRY_NAME);
+            if (!Arrays.equals(newManifest.contents, emittedSignatureManifest)) {
+                // Emitted v1 signature is no longer valid.
+                try {
+                    signatureZipEntries =
+                            V1SchemeSigner.signManifest(
+                                    mV1SignerConfigs,
+                                    mV1ContentDigestAlgorithm,
+                                    apkSigningSchemeIds,
+                                    newManifest);
+                } catch (CertificateEncodingException e) {
+                    throw new SignatureException("Failed to generate v1 signature", e);
+                }
+            } else {
+                // Emitted v1 signature is still valid. Check whether the signature is there in the
+                // output.
+                signatureZipEntries = new ArrayList<>();
+                for (Map.Entry<String, byte[]> expectedOutputEntry
+                        : mEmittedSignatureJarEntryData.entrySet()) {
+                    String entryName = expectedOutputEntry.getKey();
+                    byte[] expectedData = expectedOutputEntry.getValue();
+                    GetJarEntryDataRequest actualDataRequest =
+                            mOutputSignatureJarEntryDataRequests.get(entryName);
+                    if (actualDataRequest == null) {
+                        // This signature entry hasn't been output.
+                        signatureZipEntries.add(Pair.of(entryName, expectedData));
+                        continue;
+                    }
+                    byte[] actualData = actualDataRequest.getData();
+                    if (!Arrays.equals(expectedData, actualData)) {
+                        signatureZipEntries.add(Pair.of(entryName, expectedData));
+                    }
+                }
+                if (signatureZipEntries.isEmpty()) {
+                    // v1 signature in the output is valid
+                    return null;
+                }
+                // v1 signature in the output is not valid.
+            }
+        }
+
+        if (signatureZipEntries.isEmpty()) {
+            // v1 signature in the output is valid
+            mV1SignaturePending = false;
+            return null;
+        }
+
+        List<OutputJarSignatureRequest.JarEntry> sigEntries =
+                new ArrayList<>(signatureZipEntries.size());
+        for (Pair<String, byte[]> entry : signatureZipEntries) {
+            String entryName = entry.getFirst();
+            byte[] entryData = entry.getSecond();
+            sigEntries.add(new OutputJarSignatureRequest.JarEntry(entryName, entryData));
+            mEmittedSignatureJarEntryData.put(entryName, entryData);
+        }
+        mAddV1SignatureRequest = new OutputJarSignatureRequestImpl(sigEntries);
+        return mAddV1SignatureRequest;
+    }
+
+    @Override
+    public OutputApkSigningBlockRequest outputZipSections(
+            DataSource zipEntries,
+            DataSource zipCentralDirectory,
+            DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException {
+        checkNotClosed();
+        checkV1SigningDoneIfEnabled();
+        if (!mV2SigningEnabled) {
+            return null;
+        }
+        invalidateV2Signature();
+
+        byte[] apkSigningBlock =
+                V2SchemeSigner.generateApkSigningBlock(
+                        zipEntries, zipCentralDirectory, zipEocd, mV2SignerConfigs);
+
+        mAddV2SignatureRequest = new OutputApkSigningBlockRequestImpl(apkSigningBlock);
+        return mAddV2SignatureRequest;
+    }
+
+    @Override
+    public void outputDone() {
+        checkNotClosed();
+        checkV1SigningDoneIfEnabled();
+        checkV2SigningDoneIfEnabled();
+    }
+
+    @Override
+    public void close() {
+        mClosed = true;
+
+        mAddV1SignatureRequest = null;
+        mInputJarManifestEntryDataRequest = null;
+        mOutputJarEntryDigestRequests.clear();
+        mOutputJarEntryDigests.clear();
+        mEmittedSignatureJarEntryData.clear();
+        mOutputSignatureJarEntryDataRequests.clear();
+
+        mAddV2SignatureRequest = null;
+    }
+
+    private void invalidateV1Signature() {
+        if (mV1SigningEnabled) {
+            mV1SignaturePending = true;
+        }
+        invalidateV2Signature();
+    }
+
+    private void invalidateV2Signature() {
+        if (mV2SigningEnabled) {
+            mV2SignaturePending = true;
+            mAddV2SignatureRequest = null;
+        }
+    }
+
+    private void checkNotClosed() {
+        if (mClosed) {
+            throw new IllegalStateException("Engine closed");
+        }
+    }
+
+    private void checkV1SigningDoneIfEnabled() {
+        if (!mV1SignaturePending) {
+            return;
+        }
+
+        if (mAddV1SignatureRequest == null) {
+            throw new IllegalStateException(
+                    "v1 signature (JAR signature) not yet generated. Skipped outputJarEntries()?");
+        }
+        if (!mAddV1SignatureRequest.isDone()) {
+            throw new IllegalStateException(
+                    "v1 signature (JAR signature) addition requested by outputJarEntries() hasn't"
+                            + " been fulfilled");
+        }
+        for (Map.Entry<String, byte[]> expectedOutputEntry
+                : mEmittedSignatureJarEntryData.entrySet()) {
+            String entryName = expectedOutputEntry.getKey();
+            byte[] expectedData = expectedOutputEntry.getValue();
+            GetJarEntryDataRequest actualDataRequest =
+                    mOutputSignatureJarEntryDataRequests.get(entryName);
+            if (actualDataRequest == null) {
+                throw new IllegalStateException(
+                        "APK entry " + entryName + " not yet output despite this having been"
+                                + " requested");
+            } else if (!actualDataRequest.isDone()) {
+                throw new IllegalStateException(
+                        "Still waiting to inspect output APK's " + entryName);
+            }
+            byte[] actualData = actualDataRequest.getData();
+            if (!Arrays.equals(expectedData, actualData)) {
+                throw new IllegalStateException(
+                        "Output APK entry " + entryName + " data differs from what was requested");
+            }
+        }
+        mV1SignaturePending = false;
+    }
+
+    private void checkV2SigningDoneIfEnabled() {
+        if (!mV2SignaturePending) {
+            return;
+        }
+        if (mAddV2SignatureRequest == null) {
+            throw new IllegalStateException(
+                    "v2 signature (APK Signature Scheme v2 signature) not yet generated."
+                            + " Skipped outputZipSections()?");
+        }
+        if (!mAddV2SignatureRequest.isDone()) {
+            throw new IllegalStateException(
+                    "v2 signature (APK Signature Scheme v2 signature) addition requested by"
+                            + " outputZipSections() hasn't been fulfilled yet");
+        }
+        mAddV2SignatureRequest = null;
+        mV2SignaturePending = false;
+    }
+
+    /**
+     * Returns the output policy for the provided input JAR entry.
+     */
+    private InputJarEntryInstructions.OutputPolicy getInputJarEntryOutputPolicy(String entryName) {
+        if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) {
+            return InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE;
+        }
+        if ((mOtherSignersSignaturesPreserved)
+                || (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName))) {
+            return InputJarEntryInstructions.OutputPolicy.OUTPUT;
+        }
+        return InputJarEntryInstructions.OutputPolicy.SKIP;
+    }
+
+    private static class OutputJarSignatureRequestImpl implements OutputJarSignatureRequest {
+        private final List<JarEntry> mAdditionalJarEntries;
+        private volatile boolean mDone;
+
+        private OutputJarSignatureRequestImpl(List<JarEntry> additionalZipEntries) {
+            mAdditionalJarEntries =
+                    Collections.unmodifiableList(new ArrayList<>(additionalZipEntries));
+        }
+
+        @Override
+        public List<JarEntry> getAdditionalJarEntries() {
+            return mAdditionalJarEntries;
+        }
+
+        @Override
+        public void done() {
+            mDone = true;
+        }
+
+        private boolean isDone() {
+            return mDone;
+        }
+    }
+
+    private static class OutputApkSigningBlockRequestImpl implements OutputApkSigningBlockRequest {
+        private final byte[] mApkSigningBlock;
+        private volatile boolean mDone;
+
+        private OutputApkSigningBlockRequestImpl(byte[] apkSigingBlock) {
+            mApkSigningBlock = apkSigingBlock.clone();
+        }
+
+        @Override
+        public byte[] getApkSigningBlock() {
+            return mApkSigningBlock.clone();
+        }
+
+        @Override
+        public void done() {
+            mDone = true;
+        }
+
+        private boolean isDone() {
+            return mDone;
+        }
+    }
+
+    /**
+     * JAR entry inspection request which obtain the entry's uncompressed data.
+     */
+    private static class GetJarEntryDataRequest implements InspectJarEntryRequest {
+        private final String mEntryName;
+        private final Object mLock = new Object();
+        private final ByteArrayOutputStreamSink mBuf = new ByteArrayOutputStreamSink();
+
+        private boolean mDone;
+
+        private GetJarEntryDataRequest(String entryName) {
+            mEntryName = entryName;
+        }
+
+        @Override
+        public String getEntryName() {
+            return mEntryName;
+        }
+
+        @Override
+        public DataSink getDataSink() {
+            synchronized (mLock) {
+                checkNotDone();
+                return mBuf;
+            }
+        }
+
+        @Override
+        public void done() {
+            synchronized (mLock) {
+                if (mDone) {
+                    return;
+                }
+                mDone = true;
+            }
+        }
+
+        private boolean isDone() {
+            synchronized (mLock) {
+                return mDone;
+            }
+        }
+
+        private void checkNotDone() throws IllegalStateException {
+            synchronized (mLock) {
+                if (mDone) {
+                    throw new IllegalStateException("Already done");
+                }
+            }
+        }
+
+        private byte[] getData() {
+            synchronized (mLock) {
+                if (!mDone) {
+                    throw new IllegalStateException("Not yet done");
+                }
+                return mBuf.getData();
+            }
+        }
+    }
+
+    /**
+     * JAR entry inspection request which obtains the digest of the entry's uncompressed data.
+     */
+    private static class GetJarEntryDataDigestRequest implements InspectJarEntryRequest {
+        private final String mEntryName;
+        private final MessageDigest mMessageDigest;
+        private final DataSink mDataSink;
+        private final Object mLock = new Object();
+
+        private boolean mDone;
+        private byte[] mDigest;
+
+        private GetJarEntryDataDigestRequest(String entryName, MessageDigest digest) {
+            mEntryName = entryName;
+            mMessageDigest = digest;
+            mDataSink = new MessageDigestSink(new MessageDigest[] {mMessageDigest});
+        }
+
+        @Override
+        public String getEntryName() {
+            return mEntryName;
+        }
+
+        @Override
+        public DataSink getDataSink() {
+            synchronized (mLock) {
+                checkNotDone();
+                return mDataSink;
+            }
+        }
+
+        @Override
+        public void done() {
+            synchronized (mLock) {
+                if (mDone) {
+                    return;
+                }
+                mDone = true;
+                mDigest = mMessageDigest.digest();
+            }
+        }
+
+        private boolean isDone() {
+            synchronized (mLock) {
+                return mDone;
+            }
+        }
+
+        private void checkNotDone() throws IllegalStateException {
+            synchronized (mLock) {
+                if (mDone) {
+                    throw new IllegalStateException("Already done");
+                }
+            }
+        }
+
+        private byte[] getDigest() {
+            synchronized (mLock) {
+                if (!mDone) {
+                    throw new IllegalStateException("Not yet done");
+                }
+                return mDigest.clone();
+            }
+        }
+    }
+
+    /**
+     * Configuration of a signer.
+     *
+     * <p>Use {@link Builder} to obtain configuration instances.
+     */
+    public static class SignerConfig {
+        private final String mName;
+        private final PrivateKey mPrivateKey;
+        private final List<X509Certificate> mCertificates;
+
+        private SignerConfig(
+                String name,
+                PrivateKey privateKey,
+                List<X509Certificate> certificates) {
+            mName = name;
+            mPrivateKey = privateKey;
+            mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
+        }
+
+        /**
+         * Returns the name of this signer.
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the signing key of this signer.
+         */
+        public PrivateKey getPrivateKey() {
+            return mPrivateKey;
+        }
+
+        /**
+         * Returns the certificate(s) of this signer. The first certificate's public key corresponds
+         * to this signer's private key.
+         */
+        public List<X509Certificate> getCertificates() {
+            return mCertificates;
+        }
+
+        /**
+         * Builder of {@link SignerConfig} instances.
+         */
+        public static class Builder {
+            private final String mName;
+            private final PrivateKey mPrivateKey;
+            private final List<X509Certificate> mCertificates;
+
+            /**
+             * Constructs a new {@code Builder}.
+             *
+             * @param name signer's name. The name is reflected in the name of files comprising the
+             *        JAR signature of the APK.
+             * @param privateKey signing key
+             * @param certificates list of one or more X.509 certificates. The subject public key of
+             *        the first certificate must correspond to the {@code privateKey}.
+             */
+            public Builder(
+                    String name,
+                    PrivateKey privateKey,
+                    List<X509Certificate> certificates) {
+                mName = name;
+                mPrivateKey = privateKey;
+                mCertificates = new ArrayList<>(certificates);
+            }
+
+            /**
+             * Returns a new {@code SignerConfig} instance configured based on the configuration of
+             * this builder.
+             */
+            public SignerConfig build() {
+                return new SignerConfig(
+                        mName,
+                        mPrivateKey,
+                        mCertificates);
+            }
+        }
+    }
+
+    /**
+     * Builder of {@link DefaultApkSignerEngine} instances.
+     */
+    public static class Builder {
+        private final List<SignerConfig> mSignerConfigs;
+        private final int mMinSdkVersion;
+
+        private boolean mV1SigningEnabled = true;
+        private boolean mV2SigningEnabled = true;
+        private boolean mOtherSignersSignaturesPreserved;
+
+        /**
+         * Constructs a new {@code Builder}.
+         *
+         * @param signerConfigs information about signers with which the APK will be signed. At
+         *        least one signer configuration must be provided.
+         * @param minSdkVersion API Level of the oldest Android platform on which the APK is
+         *        supposed to be installed. See {@code minSdkVersion} attribute in the APK's
+         *        {@code AndroidManifest.xml}. The higher the version, the stronger signing features
+         *        will be enabled.
+         */
+        public Builder(
+                List<SignerConfig> signerConfigs,
+                int minSdkVersion) {
+            if (signerConfigs.isEmpty()) {
+                throw new IllegalArgumentException("At least one signer config must be provided");
+            }
+            mSignerConfigs = new ArrayList<>(signerConfigs);
+            mMinSdkVersion = minSdkVersion;
+        }
+
+        /**
+         * Returns a new {@code DefaultApkSignerEngine} instance configured based on the
+         * configuration of this builder.
+         */
+        public DefaultApkSignerEngine build() throws InvalidKeyException {
+            return new DefaultApkSignerEngine(
+                    mSignerConfigs,
+                    mMinSdkVersion,
+                    mV1SigningEnabled,
+                    mV2SigningEnabled,
+                    mOtherSignersSignaturesPreserved);
+        }
+
+        /**
+         * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme).
+         *
+         * <p>By default, the APK will be signed using this scheme.
+         */
+        public Builder setV1SigningEnabled(boolean enabled) {
+            mV1SigningEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature
+         * scheme).
+         *
+         * <p>By default, the APK will be signed using this scheme.
+         */
+        public Builder setV2SigningEnabled(boolean enabled) {
+            mV2SigningEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether signatures produced by signers other than the ones configured in this engine
+         * should be copied from the input APK to the output APK.
+         *
+         * <p>By default, signatures of other signers are omitted from the output APK.
+         */
+        public Builder setOtherSignersSignaturesPreserved(boolean preserved) {
+            mOtherSignersSignaturesPreserved = preserved;
+            return this;
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
index 182b4ed..9ef04bf 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
@@ -24,11 +24,11 @@
  * Data sink which feeds all received data into the associated {@link MessageDigest} instances. Each
  * {@code MessageDigest} instance receives the same data.
  */
-class MessageDigestSink implements DataSink {
+public class MessageDigestSink implements DataSink {
 
     private final MessageDigest[] mMessageDigests;
 
-    MessageDigestSink(MessageDigest[] digests) {
+    public MessageDigestSink(MessageDigest[] digests) {
         mMessageDigests = digests;
     }
 
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteArrayOutputStreamSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteArrayOutputStreamSink.java
new file mode 100644
index 0000000..ca79df7
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteArrayOutputStreamSink.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 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.apksigner.core.internal.util;
+
+import com.android.apksigner.core.util.DataSink;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Data sink which stores all input data into an internal {@link ByteArrayOutputStream}, thus
+ * accepting an arbitrary amount of data.
+ */
+public class ByteArrayOutputStreamSink implements DataSink {
+
+    private final ByteArrayOutputStream mBuf = new ByteArrayOutputStream();
+
+    @Override
+    public void consume(byte[] buf, int offset, int length) {
+        mBuf.write(buf, offset, length);
+    }
+
+    @Override
+    public void consume(ByteBuffer buf) {
+        if (!buf.hasRemaining()) {
+            return;
+        }
+
+        if (buf.hasArray()) {
+            mBuf.write(
+                    buf.array(),
+                    buf.arrayOffset() + buf.position(),
+                    buf.remaining());
+            buf.position(buf.limit());
+        } else {
+            byte[] tmp = new byte[buf.remaining()];
+            buf.get(tmp);
+            mBuf.write(tmp, 0, tmp.length);
+        }
+    }
+
+    /**
+     * Returns the data received so far.
+     */
+    public byte[] getData() {
+        return mBuf.toByteArray();
+    }
+}
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 4ff8c43..abb23d1 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -378,6 +378,8 @@
     build_command.extend(["-m", prop_dict["mount_point"]])
     if target_out:
       build_command.extend(["-d", target_out])
+    if fs_config:
+      build_command.extend(["-C", fs_config])
     if "selinux_fc" in prop_dict:
       build_command.extend(["-c", prop_dict["selinux_fc"]])
     if "squashfs_compressor" in prop_dict:
diff --git a/tools/warn.py b/tools/warn.py
index 135cd51..a4a9e16 100755
--- a/tools/warn.py
+++ b/tools/warn.py
@@ -312,87 +312,1001 @@
         'description':'Java: Unchecked conversion',
         'patterns':[r".*: warning: \[unchecked\] unchecked conversion"] },
 
-    # Warnings from error prone.
-    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
-        'description':'Java: Long literal suffix',
-        'patterns':[r".*: warning: \[LongLiteralLowerCaseSuffix\] Prefer 'L' to 'l' for the suffix to long literal"] },
-    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
-        'description':'Java: Missing @Deprecated',
-        'patterns':[r".*: warning: \[DepAnn\] Deprecated item is not annotated with @Deprecated"] },
-    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
-        'description':'Java: Use of deprecated member',
-        'patterns':[r".*: warning: \[deprecation\] .+ in .+ has been deprecated"] },
-    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
-        'description':'Java: Missing hashCode method',
-        'patterns':[r".*: warning: \[EqualsHashCode\] Classes that override equals should also override hashCode."] },
-    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
-        'description':'Java: Hashtable contains is a legacy method',
-        'patterns':[r".*: warning: \[HashtableContains\] contains\(\) is a legacy method that is equivalent to containsValue\(\)"] },
-    { 'category':'java',    'severity':severity.LOW,   'members':[], 'option':'',
-        'description':'Java: Type parameter used only for return type',
-        'patterns':[r".*: warning: \[TypeParameterUnusedInFormals\] Declaring a type parameter that is only used in the return type is a misuse of generics: operations on the type parameter are unchecked, it hides unsafe casts at invocations of the method, and it interacts badly with method overload resolution."] },
+    # Warnings from Error Prone.
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description': 'Java: Use of deprecated member',
+     'patterns': [r'.*: warning: \[deprecation\] .+']},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description': 'Java: Unchecked conversion',
+     'patterns': [r'.*: warning: \[unchecked\] .+']},
 
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: reference equality used on arrays',
-        'patterns':[r".*: warning: \[ArrayEquals\] Reference equality used to compare arrays"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: hashcode used on array',
-        'patterns':[r".*: warning: \[ArrayHashCode\] hashcode method on array does not hash array contents"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: toString used on an array',
-        'patterns':[r".*: warning: \[ArrayToStringConcatenation\] Implicit toString used on an array \(String \+ Array\)",
-                    r".*: warning: \[ArrayToString\] Calling toString on an array does not provide useful information"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Exception created but not thrown',
-        'patterns':[r".*: warning: \[DeadException\] Exception created but not thrown"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Return or throw from a finally',
-        'patterns':[r".*: warning: \[Finally\] If you return or throw from a finally, then values returned or thrown from the try-catch block will be ignored. Consider using try-with-resources instead."] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Erroneous use of @GuardedBy',
-        'patterns':[r".*: warning: \[GuardedByChecker\] This access should be guarded by '.+'; instead found: '.+'",
-                    r".*: warning: \[GuardedByChecker\] This access should be guarded by '.+', which is not currently held"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Mislabeled Android string',
-        'patterns':[r".*: warning: \[MislabeledAndroidString\] .+ is not \".+\" but \".+\"; prefer .+ for clarity"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Missing cases in enum switch',
-        'patterns':[r".*: warning: \[MissingCasesInEnumSwitch\] Non-exhaustive switch, expected cases for: .+"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Multiple top-level classes (inhibits bug analysis)',
-        'patterns':[r".*: warning: \[MultipleTopLevelClasses\] Expected at most one top-level class declaration, instead found: .+"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: equals method doesn\'t override Object.equals',
-        'patterns':[r".*: warning: \[NonOverridingEquals\] equals method doesn't override Object\.equals.*"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Update of a volatile variable is non-atomic',
-        'patterns':[r".*: warning: \[NonAtomicVolatileUpdate\] This update of a volatile variable is non-atomic"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Return value ignored',
-        'patterns':[r".*: warning: \[ReturnValueIgnored\] Return value of this method must be used",
-                    r".*: warning: \[RectIntersectReturnValueIgnored\] Return value of android.graphics.Rect.intersect\(\) must be checked"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Static variable accessed from an object instance',
-        'patterns':[r".*: warning: \[StaticAccessedFromInstance\] Static (method|variable) .+ should not be accessed from an object instance; instead use .+"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Static guarded by instance',
-        'patterns':[r".*: warning: \[StaticGuardedByInstance\] Write to static variable should not be guarded by instance lock '.+'"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: String reference equality',
-        'patterns':[r".*: warning: \[StringEquality\] String comparison using reference equality instead of value equality"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Synchronization on non-final field',
-        'patterns':[r".*: warning: \[SynchronizeOnNonFinalField\] Synchronizing on non-final fields is not safe: if the field is ever updated, different threads may end up locking on different objects."] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Catch masks fail or assert',
-        'patterns':[r".*: warning: \[TryFailThrowable\] Catching Throwable/Error masks failures from fail\(\) or assert\*\(\) in the try block"] },
-    { 'category':'java',    'severity':severity.MEDIUM,   'members':[], 'option':'',
-        'description':'Java: Wait not in a loop',
-        'patterns':[r".*: warning: \[WaitNotInLoop\] Because of spurious wakeups, a?wait.*\(.*\) must always be called in a loop"] },
+    # Warnings from Error Prone (auto generated list).
+    {'category': 'java',
+     'severity': severity.LOW,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Deprecated item is not annotated with @Deprecated',
+     'patterns': [r".*: warning: \[DepAnn\] .+"]},
+    {'category': 'java',
+     'severity': severity.LOW,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Fallthrough warning suppression has no effect if warning is suppressed',
+     'patterns': [r".*: warning: \[FallthroughSuppression\] .+"]},
+    {'category': 'java',
+     'severity': severity.LOW,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Prefer \'L\' to \'l\' for the suffix to long literals',
+     'patterns': [r".*: warning: \[LongLiteralLowerCaseSuffix\] .+"]},
+    {'category': 'java',
+     'severity': severity.LOW,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @Binds is a more efficient and declaritive mechanism for delegating a binding.',
+     'patterns': [r".*: warning: \[UseBinds\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Assertions may be disabled at runtime and do not guarantee that execution will halt here; consider throwing an exception instead',
+     'patterns': [r".*: warning: \[AssertFalse\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Classes that implement Annotation must override equals and hashCode. Consider using AutoAnnotation instead of implementing Annotation by hand.',
+     'patterns': [r".*: warning: \[BadAnnotationImplementation\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: BigDecimal(double) and BigDecimal.valueOf(double) may lose precision, prefer BigDecimal(String) or BigDecimal(long)',
+     'patterns': [r".*: warning: \[BigDecimalLiteralDouble\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Mockito cannot mock final classes',
+     'patterns': [r".*: warning: \[CannotMockFinalClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This code, which counts elements using a loop, can be replaced by a simpler library method',
+     'patterns': [r".*: warning: \[ElementsCountedInLoop\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Empty top-level type declaration',
+     'patterns': [r".*: warning: \[EmptyTopLevelDeclaration\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Classes that override equals should also override hashCode.',
+     'patterns': [r".*: warning: \[EqualsHashCode\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: An equality test between objects with incompatible types always returns false',
+     'patterns': [r".*: warning: \[EqualsIncompatibleType\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: If you return or throw from a finally, then values returned or thrown from the try-catch block will be ignored. Consider using try-with-resources instead.',
+     'patterns': [r".*: warning: \[Finally\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This annotation has incompatible modifiers as specified by its @IncompatibleModifiers annotation',
+     'patterns': [r".*: warning: \[IncompatibleModifiers\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Class should not implement both `Iterable` and `Iterator`',
+     'patterns': [r".*: warning: \[IterableAndIterator\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Floating-point comparison without error tolerance',
+     'patterns': [r".*: warning: \[JUnit3FloatingPointComparisonWithoutDelta\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Test class inherits from JUnit 3\'s TestCase but has JUnit 4 @Test annotations.',
+     'patterns': [r".*: warning: \[JUnitAmbiguousTestClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Enum switch statement is missing cases',
+     'patterns': [r".*: warning: \[MissingCasesInEnumSwitch\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Not calling fail() when expecting an exception masks bugs',
+     'patterns': [r".*: warning: \[MissingFail\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: method overrides method in supertype; expected @Override',
+     'patterns': [r".*: warning: \[MissingOverride\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Source files should not contain multiple top-level class declarations',
+     'patterns': [r".*: warning: \[MultipleTopLevelClasses\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This update of a volatile variable is non-atomic',
+     'patterns': [r".*: warning: \[NonAtomicVolatileUpdate\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Static import of member uses non-canonical name',
+     'patterns': [r".*: warning: \[NonCanonicalStaticMemberImport\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: equals method doesn\'t override Object.equals',
+     'patterns': [r".*: warning: \[NonOverridingEquals\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Constructors should not be annotated with @Nullable since they cannot return null',
+     'patterns': [r".*: warning: \[NullableConstructor\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @Nullable should not be used for primitive types since they cannot be null',
+     'patterns': [r".*: warning: \[NullablePrimitive\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: void-returning methods should not be annotated with @Nullable, since they cannot return null',
+     'patterns': [r".*: warning: \[NullableVoid\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Package names should match the directory they are declared in',
+     'patterns': [r".*: warning: \[PackageLocation\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Second argument to Preconditions.* is a call to String.format(), which can be unwrapped',
+     'patterns': [r".*: warning: \[PreconditionsErrorMessageEagerEvaluation\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Preconditions only accepts the %s placeholder in error message strings',
+     'patterns': [r".*: warning: \[PreconditionsInvalidPlaceholder\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Passing a primitive array to a varargs method is usually wrong',
+     'patterns': [r".*: warning: \[PrimitiveArrayPassedToVarargsMethod\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Protobuf fields cannot be null, so this check is redundant',
+     'patterns': [r".*: warning: \[ProtoFieldPreconditionsCheckNotNull\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This annotation is missing required modifiers as specified by its @RequiredModifiers annotation',
+     'patterns': [r".*: warning: \[RequiredModifiers\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: A static variable or method should not be accessed from an object instance',
+     'patterns': [r".*: warning: \[StaticAccessedFromInstance\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: String comparison using reference equality instead of value equality',
+     'patterns': [r".*: warning: \[StringEquality\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Declaring a type parameter that is only used in the return type is a misuse of generics: operations on the type parameter are unchecked, it hides unsafe casts at invocations of the method, and it interacts badly with method overload resolution.',
+     'patterns': [r".*: warning: \[TypeParameterUnusedInFormals\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Using static imports for types is unnecessary',
+     'patterns': [r".*: warning: \[UnnecessaryStaticImport\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Unsynchronized method overrides a synchronized method.',
+     'patterns': [r".*: warning: \[UnsynchronizedOverridesSynchronized\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Non-constant variable missing @Var annotation',
+     'patterns': [r".*: warning: \[Var\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Because of spurious wakeups, Object.wait() and Condition.await() must always be called in a loop',
+     'patterns': [r".*: warning: \[WaitNotInLoop\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Subclasses of Fragment must be instantiable via Class#newInstance(): the class must be public, static and have a public nullary constructor',
+     'patterns': [r".*: warning: \[FragmentNotInstantiable\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Hardcoded reference to /sdcard',
+     'patterns': [r".*: warning: \[HardCodedSdCardPath\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Incompatible type as argument to Object-accepting Java collections method',
+     'patterns': [r".*: warning: \[CollectionIncompatibleType\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @AssistedInject and @Inject should not be used on different constructors in the same class.',
+     'patterns': [r".*: warning: \[AssistedInjectAndInjectOnConstructors\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Although Guice allows injecting final fields, doing so is not recommended because the injected value may not be visible to other threads.',
+     'patterns': [r".*: warning: \[GuiceInjectOnFinalField\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This method is not annotated with @Inject, but it overrides a method that is annotated with @com.google.inject.Inject. Guice will inject this method, and it is recommended to annotate it explicitly.',
+     'patterns': [r".*: warning: \[OverridesGuiceInjectableMethod\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Double-checked locking on non-volatile fields is unsafe',
+     'patterns': [r".*: warning: \[DoubleCheckedLocking\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Writes to static fields should not be guarded by instance locks',
+     'patterns': [r".*: warning: \[StaticGuardedByInstance\] .+"]},
+    {'category': 'java',
+     'severity': severity.MEDIUM,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Synchronizing on non-final fields is not safe: if the field is ever updated, different threads may end up locking on different objects.',
+     'patterns': [r".*: warning: \[SynchronizeOnNonFinalField\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Reference equality used to compare arrays',
+     'patterns': [r".*: warning: \[ArrayEquals\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: hashcode method on array does not hash array contents',
+     'patterns': [r".*: warning: \[ArrayHashCode\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Calling toString on an array does not provide useful information',
+     'patterns': [r".*: warning: \[ArrayToString.*\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Arrays.asList does not autobox primitive arrays, as one might expect.',
+     'patterns': [r".*: warning: \[ArraysAsListPrimitiveArray\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: AsyncCallable should not return a null Future, only a Future whose result is null.',
+     'patterns': [r".*: warning: \[AsyncCallableReturnsNull\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: AsyncFunction should not return a null Future, only a Future whose result is null.',
+     'patterns': [r".*: warning: \[AsyncFunctionReturnsNull\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Possible sign flip from narrowing conversion',
+     'patterns': [r".*: warning: \[BadComparable\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Shift by an amount that is out of range',
+     'patterns': [r".*: warning: \[BadShiftAmount\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: valueOf provides better time and space performance',
+     'patterns': [r".*: warning: \[BoxedPrimitiveConstructor\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: The called constructor accepts a parameter with the same name and type as one of its caller\'s parameters, but its caller doesn\'t pass that parameter to it.  It\'s likely that it was intended to.',
+     'patterns': [r".*: warning: \[ChainingConstructorIgnoresParameter\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Ignored return value of method that is annotated with @CheckReturnValue',
+     'patterns': [r".*: warning: \[CheckReturnValue\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Inner class is non-static but does not reference enclosing class',
+     'patterns': [r".*: warning: \[ClassCanBeStatic\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: The source file name should match the name of the top-level class it contains',
+     'patterns': [r".*: warning: \[ClassName\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This comparison method violates the contract',
+     'patterns': [r".*: warning: \[ComparisonContractViolated\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Comparison to value that is out of range for the compared type',
+     'patterns': [r".*: warning: \[ComparisonOutOfRange\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Non-compile-time constant expression passed to parameter with @CompileTimeConstant type annotation.',
+     'patterns': [r".*: warning: \[CompileTimeConstant\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Exception created but not thrown',
+     'patterns': [r".*: warning: \[DeadException\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Division by integer literal zero',
+     'patterns': [r".*: warning: \[DivZero\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Empty statement after if',
+     'patterns': [r".*: warning: \[EmptyIf\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: == NaN always returns false; use the isNaN methods instead',
+     'patterns': [r".*: warning: \[EqualsNaN\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Method annotated @ForOverride must be protected or package-private and only invoked from declaring class',
+     'patterns': [r".*: warning: \[ForOverride\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Futures.getChecked requires a checked exception type with a standard constructor.',
+     'patterns': [r".*: warning: \[FuturesGetCheckedIllegalExceptionType\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Calling getClass() on an object of type Class returns the Class object for java.lang.Class; you probably meant to operate on the object directly',
+     'patterns': [r".*: warning: \[GetClassOnClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: An object is tested for equality to itself using Guava Libraries',
+     'patterns': [r".*: warning: \[GuavaSelfEquals\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: contains() is a legacy method that is equivalent to containsValue()',
+     'patterns': [r".*: warning: \[HashtableContains\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Cipher.getInstance() is invoked using either the default settings or ECB mode',
+     'patterns': [r".*: warning: \[InsecureCipherMode\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Invalid syntax used for a regular expression',
+     'patterns': [r".*: warning: \[InvalidPatternSyntax\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: The argument to Class#isInstance(Object) should not be a Class',
+     'patterns': [r".*: warning: \[IsInstanceOfClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: jMock tests must have a @RunWith(JMock.class) annotation, or the Mockery field must have a @Rule JUnit annotation',
+     'patterns': [r".*: warning: \[JMockTestWithoutRunWithOrRuleAnnotation\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Test method will not be run; please prefix name with "test"',
+     'patterns': [r".*: warning: \[JUnit3TestNotRun\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: setUp() method will not be run; Please add a @Before annotation',
+     'patterns': [r".*: warning: \[JUnit4SetUpNotRun\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: tearDown() method will not be run; Please add an @After annotation',
+     'patterns': [r".*: warning: \[JUnit4TearDownNotRun\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Test method will not be run; please add @Test annotation',
+     'patterns': [r".*: warning: \[JUnit4TestNotRun\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Printf-like format string does not match its arguments',
+     'patterns': [r".*: warning: \[MalformedFormatString\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Use of "YYYY" (week year) in a date pattern without "ww" (week in year). You probably meant to use "yyyy" (year) instead.',
+     'patterns': [r".*: warning: \[MisusedWeekYear\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: A bug in Mockito will cause this test to fail at runtime with a ClassCastException',
+     'patterns': [r".*: warning: \[MockitoCast\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Missing method call for verify(mock) here',
+     'patterns': [r".*: warning: \[MockitoUsage\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Modifying a collection with itself',
+     'patterns': [r".*: warning: \[ModifyingCollectionWithItself\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Compound assignments to bytes, shorts, chars, and floats hide dangerous casts',
+     'patterns': [r".*: warning: \[NarrowingCompoundAssignment\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @NoAllocation was specified on this method, but something was found that would trigger an allocation',
+     'patterns': [r".*: warning: \[NoAllocation\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Static import of type uses non-canonical name',
+     'patterns': [r".*: warning: \[NonCanonicalStaticImport\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @CompileTimeConstant parameters should be final',
+     'patterns': [r".*: warning: \[NonFinalCompileTimeConstant\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Calling getAnnotation on an annotation that is not retained at runtime.',
+     'patterns': [r".*: warning: \[NonRuntimeAnnotation\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Numeric comparison using reference equality instead of value equality',
+     'patterns': [r".*: warning: \[NumericEquality\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Comparison using reference equality instead of value equality',
+     'patterns': [r".*: warning: \[OptionalEquality\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Varargs doesn\'t agree for overridden method',
+     'patterns': [r".*: warning: \[Overrides\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Literal passed as first argument to Preconditions.checkNotNull() can never be null',
+     'patterns': [r".*: warning: \[PreconditionsCheckNotNull\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: First argument to `Preconditions.checkNotNull()` is a primitive rather than an object reference',
+     'patterns': [r".*: warning: \[PreconditionsCheckNotNullPrimitive\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Protobuf fields cannot be null',
+     'patterns': [r".*: warning: \[ProtoFieldNullComparison\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Comparing protobuf fields of type String using reference equality',
+     'patterns': [r".*: warning: \[ProtoStringFieldReferenceEquality\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java:  Check for non-whitelisted callers to RestrictedApiChecker.',
+     'patterns': [r".*: warning: \[RestrictedApiChecker\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Return value of this method must be used',
+     'patterns': [r".*: warning: \[ReturnValueIgnored\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Variable assigned to itself',
+     'patterns': [r".*: warning: \[SelfAssignment\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: An object is compared to itself',
+     'patterns': [r".*: warning: \[SelfComparision\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Variable compared to itself',
+     'patterns': [r".*: warning: \[SelfEquality\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: An object is tested for equality to itself',
+     'patterns': [r".*: warning: \[SelfEquals\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Comparison of a size >= 0 is always true, did you intend to check for non-emptiness?',
+     'patterns': [r".*: warning: \[SizeGreaterThanOrEqualsZero\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Calling toString on a Stream does not provide useful information',
+     'patterns': [r".*: warning: \[StreamToString\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: StringBuilder does not have a char constructor; this invokes the int constructor.',
+     'patterns': [r".*: warning: \[StringBuilderInitWithChar\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Suppressing "deprecated" is probably a typo for "deprecation"',
+     'patterns': [r".*: warning: \[SuppressWarningsDeprecated\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: throwIfUnchecked(knownCheckedException) is a no-op.',
+     'patterns': [r".*: warning: \[ThrowIfUncheckedKnownChecked\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Catching Throwable/Error masks failures from fail() or assert*() in the try block',
+     'patterns': [r".*: warning: \[TryFailThrowable\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Type parameter used as type qualifier',
+     'patterns': [r".*: warning: \[TypeParameterQualifier\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Non-generic methods should not be invoked with type arguments',
+     'patterns': [r".*: warning: \[UnnecessaryTypeArgument\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Instance created but never used',
+     'patterns': [r".*: warning: \[UnusedAnonymousClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Use of wildcard imports is forbidden',
+     'patterns': [r".*: warning: \[WildcardImport\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Method parameter has wrong package',
+     'patterns': [r".*: warning: \[ParameterPackage\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Certain resources in `android.R.string` have names that do not match their content',
+     'patterns': [r".*: warning: \[MislabeledAndroidString\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Return value of android.graphics.Rect.intersect() must be checked',
+     'patterns': [r".*: warning: \[RectIntersectReturnValueIgnored\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Invalid printf-style format string',
+     'patterns': [r".*: warning: \[FormatString\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @AssistedInject and @Inject cannot be used on the same constructor.',
+     'patterns': [r".*: warning: \[AssistedInjectAndInjectOnSameConstructor\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Injected constructors cannot be optional nor have binding annotations',
+     'patterns': [r".*: warning: \[InjectedConstructorAnnotations\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: The target of a scoping annotation must be set to METHOD and/or TYPE.',
+     'patterns': [r".*: warning: \[InjectInvalidTargetingOnScopingAnnotation\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Abstract methods are not injectable with javax.inject.Inject.',
+     'patterns': [r".*: warning: \[JavaxInjectOnAbstractMethod\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: @javax.inject.Inject cannot be put on a final field.',
+     'patterns': [r".*: warning: \[JavaxInjectOnFinalField\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: A class may not have more than one injectable constructor.',
+     'patterns': [r".*: warning: \[MoreThanOneInjectableConstructor\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Using more than one qualifier annotation on the same element is not allowed.',
+     'patterns': [r".*: warning: \[InjectMoreThanOneQualifier\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: A class can be annotated with at most one scope annotation',
+     'patterns': [r".*: warning: \[InjectMoreThanOneScopeAnnotationOnClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Annotations cannot be both Qualifiers/BindingAnnotations and Scopes',
+     'patterns': [r".*: warning: \[OverlappingQualifierAndScopeAnnotation\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Scope annotation on an interface or abstact class is not allowed',
+     'patterns': [r".*: warning: \[InjectScopeAnnotationOnInterfaceOrAbstractClass\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Scoping and qualifier annotations must have runtime retention.',
+     'patterns': [r".*: warning: \[InjectScopeOrQualifierAnnotationRetention\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Dagger @Provides methods may not return null unless annotated with @Nullable',
+     'patterns': [r".*: warning: \[DaggerProvidesNull\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Scope annotation on implementation class of AssistedInject factory is not allowed',
+     'patterns': [r".*: warning: \[GuiceAssistedInjectScoping\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: A constructor cannot have two @Assisted parameters of the same type unless they are disambiguated with named @Assisted annotations. ',
+     'patterns': [r".*: warning: \[GuiceAssistedParameters\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This method is not annotated with @Inject, but it overrides a  method that is  annotated with @javax.inject.Inject.',
+     'patterns': [r".*: warning: \[OverridesJavaxInjectableMethod\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Checks for unguarded accesses to fields and methods with @GuardedBy annotations',
+     'patterns': [r".*: warning: \[GuardedByChecker\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Invalid @GuardedBy expression',
+     'patterns': [r".*: warning: \[GuardedByValidator\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: Type declaration annotated with @Immutable is not immutable',
+     'patterns': [r".*: warning: \[Immutable\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This method does not acquire the locks specified by its @LockMethod annotation',
+     'patterns': [r".*: warning: \[LockMethodChecker\] .+"]},
+    {'category': 'java',
+     'severity': severity.HIGH,
+     'members': [],
+     'option': '',
+     'description':
+         'Java: This method does not acquire the locks specified by its @UnlockMethod annotation',
+     'patterns': [r".*: warning: \[UnlockMethod\] .+"]},
 
-    { 'category':'java',    'severity':severity.UNKNOWN,   'members':[], 'option':'',
-        'description':'Java: Unclassified/unrecognized warnings',
-        'patterns':[r".*: warning: \[.+\] .+"] },
+    {'category': 'java',
+     'severity': severity.UNKNOWN,
+     'members': [],
+     'option': '',
+     'description': 'Java: Unclassified/unrecognized warnings',
+     'patterns': [r".*: warning: \[.+\] .+"]},
 
     { 'category':'aapt',    'severity':severity.MEDIUM,   'members':[], 'option':'',
         'description':'aapt: No default translation',