ProtoLog: Cache the result of ProtoLogImpl.isEnabled(ProtoLogGroup)

We introduce a generated class ProtoLog$Cache, which contains a field
for each ProtoLogGroup to cache whether it is enabled or not.

This both makes lookup faster, and reduces code size.

Test: make droid
Change-Id: I581c87849c875918b182eff85996b6d19a6755ec
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4f4e47a..3067beb 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -21,6 +21,7 @@
     cmd: "$(location protologtool) transform-protolog-calls " +
       "--protolog-class com.android.server.protolog.common.ProtoLog " +
       "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
+      "--protolog-cache-class 'com.android.server.protolog.ProtoLog$$Cache' " +
       "--loggroups-class com.android.server.wm.ProtoLogGroup " +
       "--loggroups-jar $(location :services.core.wm.protologgroups) " +
       "--output-srcjar $(out) " +
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
index 20bab55..bd46c0ae 100644
--- a/services/core/java/com/android/server/protolog/ProtoLogImpl.java
+++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
@@ -45,7 +45,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.IllegalFormatConversionException;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -57,8 +56,18 @@
 public class ProtoLogImpl {
     private static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
 
+    /**
+     * A runnable to update the cached output of {@link #isEnabled}.
+     *
+     * Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
+     * starting / stopping proto log, or enabling / disabling log groups.
+     */
+    static Runnable sCacheUpdater = () -> { };
+
     private static void addLogGroupEnum(IProtoLogGroup[] config) {
-        Arrays.stream(config).forEach(group -> LOG_GROUPS.put(group.name(), group));
+        for (IProtoLogGroup group : config) {
+            LOG_GROUPS.put(group.name(), group);
+        }
     }
 
     static {
@@ -303,6 +312,7 @@
             mProtoLogEnabled = true;
             mProtoLogEnabledLockFree = true;
         }
+        sCacheUpdater.run();
     }
 
     /**
@@ -327,6 +337,7 @@
                 throw new IllegalStateException("logging enabled while waiting for flush.");
             }
         }
+        sCacheUpdater.run();
     }
 
     /**
@@ -351,6 +362,7 @@
                 return -1;
             }
         }
+        sCacheUpdater.run();
         return 0;
     }
 
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index 3dfa4d2..bfbbf7a 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -27,19 +27,21 @@
 
         private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
         private const val PROTOLOGIMPL_CLASS_PARAM = "--protolog-impl-class"
+        private const val PROTOLOGCACHE_CLASS_PARAM = "--protolog-cache-class"
         private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
         private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
         private const val VIEWER_CONFIG_JSON_PARAM = "--viewer-conf"
         private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar"
         private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGIMPL_CLASS_PARAM,
-                PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM, VIEWER_CONFIG_JSON_PARAM,
-                OUTPUT_SOURCE_JAR_PARAM)
+                PROTOLOGCACHE_CLASS_PARAM, PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM,
+                VIEWER_CONFIG_JSON_PARAM, OUTPUT_SOURCE_JAR_PARAM)
 
         val USAGE = """
             Usage: ${Constants.NAME} <command> [<args>]
             Available commands:
 
             $TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGIMPL_CLASS_PARAM
+                <class name> $PROTOLOGCACHE_CLASS_PARAM
                 <class name> $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM
                 <config.jar> $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>]
             - processes java files replacing stub calls with logging code.
@@ -54,7 +56,7 @@
         """.trimIndent()
 
         private fun validateClassName(name: String): String {
-            if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9]+)$", name)) {
+            if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9$]+)$", name)) {
                 throw InvalidCommandException("Invalid class name $name")
             }
             return name
@@ -121,6 +123,7 @@
     val protoLogClassNameArg: String
     val protoLogGroupsClassNameArg: String
     val protoLogImplClassNameArg: String
+    val protoLogCacheClassNameArg: String
     val protoLogGroupsJarArg: String
     val viewerConfigJsonArg: String
     val outputSourceJarArg: String
@@ -170,6 +173,8 @@
                         params))
                 protoLogImplClassNameArg = validateClassName(getParam(PROTOLOGIMPL_CLASS_PARAM,
                         params))
+                protoLogCacheClassNameArg = validateClassName(getParam(PROTOLOGCACHE_CLASS_PARAM,
+                        params))
                 protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
                 viewerConfigJsonArg = validateNotSpecified(VIEWER_CONFIG_JSON_PARAM, params)
                 outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params))
@@ -181,6 +186,7 @@
                 protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
                         params))
                 protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+                protoLogCacheClassNameArg = validateNotSpecified(PROTOLOGCACHE_CLASS_PARAM, params)
                 protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
                 viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
                 outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
@@ -191,6 +197,7 @@
                 protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params)
                 protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params)
                 protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+                protoLogCacheClassNameArg = validateNotSpecified(PROTOLOGCACHE_CLASS_PARAM, params)
                 protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params)
                 viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
                 outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index a8b9839..629f720 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -53,7 +53,8 @@
 
         command.javaSourceArgs.map { path ->
             executor.submitCallable {
-                val transformer = SourceTransformer(command.protoLogImplClassNameArg, processor)
+                val transformer = SourceTransformer(command.protoLogImplClassNameArg,
+                        command.protoLogCacheClassNameArg, processor)
                 val file = File(path)
                 val text = file.readText()
                 val outSrc = try {
@@ -80,10 +81,53 @@
 
         executor.shutdown()
 
+        val cacheSplit = command.protoLogCacheClassNameArg.split(".")
+        val cacheName = cacheSplit.last()
+        val cachePackage = cacheSplit.dropLast(1).joinToString(".")
+        val cachePath = "gen/${cacheSplit.joinToString("/")}.java"
+
+        outJar.putNextEntry(ZipEntry(cachePath))
+        outJar.write(generateLogGroupCache(cachePackage, cacheName, groups,
+                command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray())
+
         outJar.close()
         out.close()
     }
 
+    fun generateLogGroupCache(
+        cachePackage: String,
+        cacheName: String,
+        groups: Map<String, LogGroup>,
+        protoLogImplClassName: String,
+        protoLogGroupsClassName: String
+    ): String {
+        val fields = groups.values.map {
+            "public static boolean ${it.name}_enabled = false;"
+        }.joinToString("\n")
+
+        val updates = groups.values.map {
+            "${it.name}_enabled = " +
+                    "$protoLogImplClassName.isEnabled($protoLogGroupsClassName.${it.name});"
+        }.joinToString("\n")
+
+        return """
+            package $cachePackage;
+
+            public class $cacheName {
+${fields.replaceIndent("                ")}
+
+                static {
+                    $protoLogImplClassName.sCacheUpdater = $cacheName::update;
+                    update();
+                }
+
+                static void update() {
+${updates.replaceIndent("                    ")}
+                }
+            }
+        """.trimIndent()
+    }
+
     private fun tryParse(code: String, fileName: String): CompilationUnit {
         try {
             return StaticJavaParser.parse(code)
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 9a38773..0ad8091 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -16,7 +16,6 @@
 
 package com.android.protolog.tool
 
-import com.android.protolog.tool.Constants.IS_ENABLED_METHOD
 import com.android.server.protolog.common.LogDataType
 import com.github.javaparser.StaticJavaParser
 import com.github.javaparser.ast.CompilationUnit
@@ -45,6 +44,7 @@
 
 class SourceTransformer(
     protoLogImplClassName: String,
+    protoLogCacheClassName: String,
     private val protoLogCallProcessor: ProtoLogCallProcessor
 ) : ProtoLogCallVisitor {
     override fun processCall(
@@ -91,10 +91,9 @@
             // Replace call to a stub method with an actual implementation.
             // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg)
             newCall.setScope(protoLogImplClassNode)
-            // Create a call to ProtoLogImpl.isEnabled(GROUP)
-            // Out: com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)
-            val isLogEnabled = MethodCallExpr(protoLogImplClassNode, IS_ENABLED_METHOD,
-                NodeList<Expression>(newCall.arguments[0].clone()))
+            // Create a call to ProtoLog$Cache.GROUP_enabled
+            // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
+            val isLogEnabled = FieldAccessExpr(protoLogCacheClassNode, "${group.name}_enabled")
             if (argTypes.size != call.arguments.size - 2) {
                 throw InvalidProtoLogCallException(
                         "Number of arguments (${argTypes.size} does not mach format" +
@@ -211,6 +210,8 @@
 
     private val protoLogImplClassNode =
             StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+    private val protoLogCacheClassNode =
+            StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogCacheClassName)
     private var processedCode: MutableList<String> = mutableListOf()
     private var offsets: IntArray = IntArray(0)
     private var fileName: String = ""
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
index 615712e..cf36651c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -30,6 +30,7 @@
         )
         private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
         private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
+        private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache"
         private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
         private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
                 "services/core/services.core.wm.protologgroups/android_common/javac/" +
@@ -56,6 +57,7 @@
     fun transformClasses() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -73,6 +75,7 @@
     fun transformClasses_noProtoLogClass() {
         val testLine = "transform-protolog-calls " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -82,6 +85,17 @@
     @Test(expected = InvalidCommandException::class)
     fun transformClasses_noProtoLogImplClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noProtoLogCacheClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -92,6 +106,7 @@
     fun transformClasses_noProtoLogGroupClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
         CommandOptions(testLine.split(' ').toTypedArray())
@@ -101,6 +116,7 @@
     fun transformClasses_noProtoLogGroupJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
         CommandOptions(testLine.split(' ').toTypedArray())
@@ -110,6 +126,7 @@
     fun transformClasses_noOutJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 TEST_JAVA_SRC.joinToString(" ")
@@ -120,6 +137,7 @@
     fun transformClasses_noJavaInput() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR"
@@ -130,6 +148,7 @@
     fun transformClasses_invalidProtoLogClass() {
         val testLine = "transform-protolog-calls --protolog-class invalid " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -140,6 +159,18 @@
     fun transformClasses_invalidProtoLogImplClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class invalid " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidProtoLogCacheClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class invalid " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -150,6 +181,7 @@
     fun transformClasses_invalidProtoLogGroupClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class invalid " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -160,6 +192,7 @@
     fun transformClasses_invalidProtoLogGroupJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar invalid.txt " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -170,6 +203,7 @@
     fun transformClasses_invalidOutJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar invalid.db ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -180,6 +214,7 @@
     fun transformClasses_invalidJavaInput() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR invalid.py"
@@ -190,6 +225,7 @@
     fun transformClasses_unknownParam() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--unknown test --protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -200,6 +236,7 @@
     fun transformClasses_noValue() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
new file mode 100644
index 0000000..ea9a58d
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 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.protolog.tool
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ProtoLogToolTest {
+
+    @Test
+    fun generateLogGroupCache() {
+        val groups = mapOf(
+                "GROUP1" to LogGroup("GROUP1", true, true, "TAG1"),
+                "GROUP2" to LogGroup("GROUP2", true, true, "TAG2")
+        )
+        val code = ProtoLogTool.generateLogGroupCache("org.example", "ProtoLog\$Cache",
+                groups, "org.example.ProtoLogImpl", "org.example.ProtoLogGroups")
+
+        assertEquals("""
+            package org.example;
+
+            public class ProtoLog${'$'}Cache {
+                public static boolean GROUP1_enabled = false;
+                public static boolean GROUP2_enabled = false;
+
+                static {
+                    org.example.ProtoLogImpl.sCacheUpdater = ProtoLog${'$'}Cache::update;
+                    update();
+                }
+
+                static void update() {
+                    GROUP1_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP1);
+                    GROUP2_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP2);
+                }
+            }
+        """.trimIndent(), code)
+    }
+}
\ No newline at end of file
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index e746300..6f5955c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -78,7 +78,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
                 }
             }
             """.trimIndent()
@@ -88,7 +88,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2); 
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2); 
             
             }
                 }
@@ -100,8 +100,8 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
                 }
             }
             """.trimIndent()
@@ -111,7 +111,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
                 }
             }
             """.trimIndent()
@@ -121,7 +121,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
                 }
             }
             """.trimIndent()
@@ -131,7 +131,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2); 
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2); 
             
             }
                 }
@@ -165,8 +165,9 @@
     }
 
     private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
-    private val implPath = "org.example.ProtoLogImpl"
-    private val sourceJarWriter = SourceTransformer(implPath, processor)
+    private val implName = "org.example.ProtoLogImpl"
+    private val cacheName = "org.example.ProtoLogCache"
+    private val sourceJarWriter = SourceTransformer(implName, cacheName, processor)
 
     private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
 
@@ -191,8 +192,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(3, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -234,8 +234,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(3, ifStmts.size)
         val ifStmt = ifStmts[1]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(3, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -273,8 +272,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(4, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
@@ -311,8 +309,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(1, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -346,8 +343,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(3, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -385,8 +381,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(4, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr