Snap for 11273583 from 0c0ed7f7abf526422d91b9d559a24b7acbc2471d to mainline-ipsec-release

Change-Id: I866fa274e27e3178de87393f2cc51709d29fca2c
diff --git a/java/Android.bp b/java/Android.bp
index 670e4ed..eee1e0e 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -66,13 +66,13 @@
     srcs: [
         "com/android/aconfig/annotations/*.java",
     ],
-    sdk_version: "current",
+    sdk_version: "core_current",
     host_supported: true,
     optimize: {
         proguard_flags_files: ["aconfig_proguard.flags"],
     },
     visibility: [
-        "//visibility:private",
+        "//visibility:public",
     ],
 }
 
@@ -80,8 +80,12 @@
     name: "framework-api-annotations",
     srcs: [
         "android/annotation/Discouraged.java",
+        "android/annotation/FlaggedApi.java",
+        "android/annotation/IntDef.java",
         "android/annotation/SystemApi.java",
         "android/annotation/TestApi.java",
+        // aconfig annotations
+        "com/android/aconfig/annotations/*.java",
     ],
 
     visibility: [
@@ -114,6 +118,7 @@
     visibility: [
         "//frameworks/libs/modules-utils/java/com/android/modules/utils",
         "//packages/modules/Bluetooth/system/binder",
+        "//packages/modules/Bluetooth/android/app/aidl",
     ],
 }
 
diff --git a/java/android/annotation/FlaggedApi.java b/java/android/annotation/FlaggedApi.java
index 9e43650..90c8e93 100644
--- a/java/android/annotation/FlaggedApi.java
+++ b/java/android/annotation/FlaggedApi.java
@@ -15,7 +15,9 @@
  */
 package android.annotation;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.TYPE;
 
@@ -24,18 +26,37 @@
 import java.lang.annotation.Target;
 
 /**
- * Indicates an API is part of a feature that is guarded by an aconfig flag.
- * </p>
- * This annotation should only appear on APIs that are marked <pre>@hide</pre>.
+ * Indicates an API is part of a feature that is guarded by an aconfig flag, and only available if
+ * the flag is enabled.
+ * <p>
+ * Unless the API has been finalized and has become part of the SDK, callers of the annotated API
+ * must check that the flag is enabled before making any assumptions about the existence of the API.
+ * <p>
+ * Example:
+ * <code><pre>
+ *     import com.example.foobar.Flags;
  *
+ *     &#64;FlaggedApi(Flags.FLAG_FOOBAR)
+ *     public void foobar() { ... }
+ * </pre></code>
+ * Usage example:
+ * <code><pre>
+ *     public void codeThatUsesFoobarApi() {
+ *         if (Flags.foobar()) {
+ *             foobar();
+ *         } else {
+ *             // gracefully handle absence of the foobar API.
+ *         }
+ *     }
+ * </pre></code>
  * @hide
  */
-@Target({TYPE, METHOD, CONSTRUCTOR})
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD, ANNOTATION_TYPE})
 @Retention(RetentionPolicy.SOURCE)
 public @interface FlaggedApi {
     /**
-     * Namespace and name of aconfig flag used to guard the feature this API is part of. Expected
-     * syntax: namespace/name, e.g. "the_namespace/the_name_of_the_flag".
+     * The aconfig flag used to guard the feature this API is part of. Use the aconfig
+     * auto-generated constant to refer to the flag, e.g. @FlaggedApi(Flags.FLAG_FOOBAR).
      */
-    String flag() default "";
+    String value();
 }
diff --git a/java/android/annotation/PermissionManuallyEnforced.java b/java/android/annotation/PermissionManuallyEnforced.java
new file mode 100644
index 0000000..5641cfc
--- /dev/null
+++ b/java/android/annotation/PermissionManuallyEnforced.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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 android.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method validates permissions manually.
+ * <p>
+ * This explicit annotation helps distinguish which of states an
+ * element may exist in:
+ * <ul>
+ * <li>Annotated with {@link EnforcePermission}, indicating that an element
+ * strictly requires one or more permissions. The verification occurs within
+ * the annotated element.
+ * <li>Annotated with {@link RequiresNoPermission}, indicating that an element
+ * requires no permissions.
+ * <li>Annotated with {@link PermissionManuallyEnforced}, indicating that the
+ * element requires some kind of permission which cannot be described using the
+ * other annotations.
+ * </ul>
+ *
+ * @see EnforcePermission
+ * @see RequiresNoPermission
+ * @hide
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface PermissionManuallyEnforced {
+}
diff --git a/java/android/annotation/UserHandleAware.java b/java/android/annotation/UserHandleAware.java
index 60dcbd8..2c3badc 100644
--- a/java/android/annotation/UserHandleAware.java
+++ b/java/android/annotation/UserHandleAware.java
@@ -38,9 +38,9 @@
  * public abstract PackageInfo getPackageInfo({@literal @}NonNull String packageName,
  *      {@literal @}PackageInfoFlags int flags) throws NameNotFoundException;
  * }</pre>
+ * This method uses {@linkplain android.content.Context#getUser()} or
+ * {@linkplain android.content.Context#getUserId()} to execute across users.
  *
- * @memberDoc This method uses {@linkplain android.content.Context#getUser}
- *            or {@linkplain android.content.Context#getUserId} to execute across users.
  * @hide
  */
 @Retention(SOURCE)
diff --git a/java/com/android/internal/util/Android.bp b/java/com/android/internal/util/Android.bp
index ea21a6b..c2de1fb 100644
--- a/java/com/android/internal/util/Android.bp
+++ b/java/com/android/internal/util/Android.bp
@@ -38,3 +38,14 @@
         "unsupportedappusage",
     ],
 }
+
+java_library {
+    name: "modules-utils-fastxmlserializer",
+    srcs: [
+        "FastXmlSerializer.java",
+    ],
+    defaults: ["modules-utils-defaults"],
+    libs: [
+        "unsupportedappusage",
+    ],
+}
diff --git a/java/com/android/internal/util/FastXmlSerializer.java b/java/com/android/internal/util/FastXmlSerializer.java
new file mode 100644
index 0000000..929c9e8
--- /dev/null
+++ b/java/com/android/internal/util/FastXmlSerializer.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2006 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.internal.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * This is a quick and dirty implementation of XmlSerializer that isn't horribly
+ * painfully slow like the normal one.  It only does what is needed for the
+ * specific XML files being written with it.
+ */
+public class FastXmlSerializer implements XmlSerializer {
+    private static final String ESCAPE_TABLE[] = new String[] {
+        "&#0;",   "&#1;",   "&#2;",   "&#3;",  "&#4;",    "&#5;",   "&#6;",  "&#7;",  // 0-7
+        "&#8;",   "&#9;",   "&#10;",  "&#11;", "&#12;",   "&#13;",  "&#14;", "&#15;", // 8-15
+        "&#16;",  "&#17;",  "&#18;",  "&#19;", "&#20;",   "&#21;",  "&#22;", "&#23;", // 16-23
+        "&#24;",  "&#25;",  "&#26;",  "&#27;", "&#28;",   "&#29;",  "&#30;", "&#31;", // 24-31
+        null,     null,     "&quot;", null,     null,     null,     "&amp;",  null,   // 32-39
+        null,     null,     null,     null,     null,     null,     null,     null,   // 40-47
+        null,     null,     null,     null,     null,     null,     null,     null,   // 48-55
+        null,     null,     null,     null,     "&lt;",   null,     "&gt;",   null,   // 56-63
+    };
+
+    private static final int DEFAULT_BUFFER_LEN = 32*1024;
+
+    private static String sSpace = "                                                              ";
+
+    private final int mBufferLen;
+    private final char[] mText;
+    private int mPos;
+
+    private Writer mWriter;
+
+    private OutputStream mOutputStream;
+    private CharsetEncoder mCharset;
+    private ByteBuffer mBytes;
+
+    private boolean mIndent = false;
+    private boolean mInTag;
+
+    private int mNesting = 0;
+    private boolean mLineStart = true;
+
+    @UnsupportedAppUsage
+    public FastXmlSerializer() {
+        this(DEFAULT_BUFFER_LEN);
+    }
+
+    /**
+     * Allocate a FastXmlSerializer with the given internal output buffer size.  If the
+     * size is zero or negative, then the default buffer size will be used.
+     *
+     * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use.
+     */
+    public FastXmlSerializer(int bufferSize) {
+        mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN;
+        mText = new char[mBufferLen];
+        mBytes = ByteBuffer.allocate(mBufferLen);
+    }
+
+    private void append(char c) throws IOException {
+        int pos = mPos;
+        if (pos >= (mBufferLen-1)) {
+            flush();
+            pos = mPos;
+        }
+        mText[pos] = c;
+        mPos = pos+1;
+    }
+
+    private void append(String str, int i, final int length) throws IOException {
+        if (length > mBufferLen) {
+            final int end = i + length;
+            while (i < end) {
+                int next = i + mBufferLen;
+                append(str, i, next<end ? mBufferLen : (end-i));
+                i = next;
+            }
+            return;
+        }
+        int pos = mPos;
+        if ((pos+length) > mBufferLen) {
+            flush();
+            pos = mPos;
+        }
+        str.getChars(i, i+length, mText, pos);
+        mPos = pos + length;
+    }
+
+    private void append(char[] buf, int i, final int length) throws IOException {
+        if (length > mBufferLen) {
+            final int end = i + length;
+            while (i < end) {
+                int next = i + mBufferLen;
+                append(buf, i, next<end ? mBufferLen : (end-i));
+                i = next;
+            }
+            return;
+        }
+        int pos = mPos;
+        if ((pos+length) > mBufferLen) {
+            flush();
+            pos = mPos;
+        }
+        System.arraycopy(buf, i, mText, pos, length);
+        mPos = pos + length;
+    }
+
+    private void append(String str) throws IOException {
+        append(str, 0, str.length());
+    }
+
+    private void appendIndent(int indent) throws IOException {
+        indent *= 4;
+        if (indent > sSpace.length()) {
+            indent = sSpace.length();
+        }
+        append(sSpace, 0, indent);
+    }
+
+    private void escapeAndAppendString(final String string) throws IOException {
+        final int N = string.length();
+        final char NE = (char)ESCAPE_TABLE.length;
+        final String[] escapes = ESCAPE_TABLE;
+        int lastPos = 0;
+        int pos;
+        for (pos=0; pos<N; pos++) {
+            char c = string.charAt(pos);
+            if (c >= NE) continue;
+            String escape = escapes[c];
+            if (escape == null) continue;
+            if (lastPos < pos) append(string, lastPos, pos-lastPos);
+            lastPos = pos + 1;
+            append(escape);
+        }
+        if (lastPos < pos) append(string, lastPos, pos-lastPos);
+    }
+
+    private void escapeAndAppendString(char[] buf, int start, int len) throws IOException {
+        final char NE = (char)ESCAPE_TABLE.length;
+        final String[] escapes = ESCAPE_TABLE;
+        int end = start+len;
+        int lastPos = start;
+        int pos;
+        for (pos=start; pos<end; pos++) {
+            char c = buf[pos];
+            if (c >= NE) continue;
+            String escape = escapes[c];
+            if (escape == null) continue;
+            if (lastPos < pos) append(buf, lastPos, pos-lastPos);
+            lastPos = pos + 1;
+            append(escape);
+        }
+        if (lastPos < pos) append(buf, lastPos, pos-lastPos);
+    }
+
+    public XmlSerializer attribute(String namespace, String name, String value) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        append(' ');
+        if (namespace != null) {
+            append(namespace);
+            append(':');
+        }
+        append(name);
+        append("=\"");
+
+        escapeAndAppendString(value);
+        append('"');
+        mLineStart = false;
+        return this;
+    }
+
+    public void cdsect(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void comment(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void docdecl(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {
+        flush();
+    }
+
+    public XmlSerializer endTag(String namespace, String name) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        mNesting--;
+        if (mInTag) {
+            append(" />\n");
+        } else {
+            if (mIndent && mLineStart) {
+                appendIndent(mNesting);
+            }
+            append("</");
+            if (namespace != null) {
+                append(namespace);
+                append(':');
+            }
+            append(name);
+            append(">\n");
+        }
+        mLineStart = true;
+        mInTag = false;
+        return this;
+    }
+
+    public void entityRef(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    private void flushBytes() throws IOException {
+        int position;
+        if ((position = mBytes.position()) > 0) {
+            mBytes.flip();
+            mOutputStream.write(mBytes.array(), 0, position);
+            mBytes.clear();
+        }
+    }
+
+    public void flush() throws IOException {
+        //Log.i("PackageManager", "flush mPos=" + mPos);
+        if (mPos > 0) {
+            if (mOutputStream != null) {
+                CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
+                CoderResult result = mCharset.encode(charBuffer, mBytes, true);
+                while (true) {
+                    if (result.isError()) {
+                        throw new IOException(result.toString());
+                    } else if (result.isOverflow()) {
+                        flushBytes();
+                        result = mCharset.encode(charBuffer, mBytes, true);
+                        continue;
+                    }
+                    break;
+                }
+                flushBytes();
+                mOutputStream.flush();
+            } else {
+                mWriter.write(mText, 0, mPos);
+                mWriter.flush();
+            }
+            mPos = 0;
+        }
+    }
+
+    public int getDepth() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean getFeature(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getName() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getNamespace() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getPrefix(String namespace, boolean generatePrefix)
+            throws IllegalArgumentException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getProperty(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void processingInstruction(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setFeature(String name, boolean state) throws IllegalArgumentException,
+            IllegalStateException {
+        if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
+            mIndent = true;
+            return;
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    public void setOutput(OutputStream os, String encoding) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (os == null)
+            throw new IllegalArgumentException();
+        if (true) {
+            try {
+                mCharset = Charset.forName(encoding).newEncoder()
+                        .onMalformedInput(CodingErrorAction.REPLACE)
+                        .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            } catch (IllegalCharsetNameException e) {
+                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
+                        encoding).initCause(e));
+            } catch (UnsupportedCharsetException e) {
+                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
+                        encoding).initCause(e));
+            }
+            mOutputStream = os;
+        } else {
+            setOutput(
+                encoding == null
+                    ? new OutputStreamWriter(os)
+                    : new OutputStreamWriter(os, encoding));
+        }
+    }
+
+    public void setOutput(Writer writer) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        mWriter = writer;
+    }
+
+    public void setPrefix(String prefix, String namespace) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setProperty(String name, Object value) throws IllegalArgumentException,
+            IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void startDocument(String encoding, Boolean standalone) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        append("<?xml version='1.0' encoding='utf-8'");
+        if (standalone != null) {
+            append(" standalone='" + (standalone ? "yes" : "no") + "'");
+        }
+        append(" ?>\n");
+        mLineStart = true;
+    }
+
+    public XmlSerializer startTag(String namespace, String name) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (mInTag) {
+            append(">\n");
+        }
+        if (mIndent) {
+            appendIndent(mNesting);
+        }
+        mNesting++;
+        append('<');
+        if (namespace != null) {
+            append(namespace);
+            append(':');
+        }
+        append(name);
+        mInTag = true;
+        mLineStart = false;
+        return this;
+    }
+
+    public XmlSerializer text(char[] buf, int start, int len) throws IOException,
+            IllegalArgumentException, IllegalStateException {
+        if (mInTag) {
+            append(">");
+            mInTag = false;
+        }
+        escapeAndAppendString(buf, start, len);
+        if (mIndent) {
+            mLineStart = buf[start+len-1] == '\n';
+        }
+        return this;
+    }
+
+    public XmlSerializer text(String text) throws IOException, IllegalArgumentException,
+            IllegalStateException {
+        if (mInTag) {
+            append(">");
+            mInTag = false;
+        }
+        escapeAndAppendString(text);
+        if (mIndent) {
+            mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n');
+        }
+        return this;
+    }
+
+}
diff --git a/java/com/android/modules/expresslog/Android.bp b/java/com/android/modules/expresslog/Android.bp
index cacc7f8..59504bd 100644
--- a/java/com/android/modules/expresslog/Android.bp
+++ b/java/com/android/modules/expresslog/Android.bp
@@ -28,12 +28,16 @@
     libs: [
         "framework-statsd",
     ],
+    static_libs: [
+        "expresslog-catalog",
+    ],
 }
 
 genrule {
     name: "statslog-expresslog-java-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module expresslog" +
-        " --javaPackage com.android.modules.expresslog --javaClass StatsExpressLog",
+        " --javaPackage com.android.modules.expresslog" +
+        " --javaClass StatsExpressLog",
     out: ["com/android/modules/expresslog/StatsExpressLog.java"],
 }
diff --git a/java/com/android/modules/expresslog/Counter.java b/java/com/android/modules/expresslog/Counter.java
index b788c3f..bcacb8b 100644
--- a/java/com/android/modules/expresslog/Counter.java
+++ b/java/com/android/modules/expresslog/Counter.java
@@ -18,8 +18,6 @@
 
 import android.annotation.NonNull;
 
-import com.android.modules.expresslog.StatsExpressLog;
-
 /** Counter encapsulates StatsD write API calls */
 public final class Counter {
 
@@ -49,7 +47,8 @@
      * @param amount to increment counter
      */
     public static void logIncrement(@NonNull String metricId, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
+        final long metricIdHash =
+                MetricIds.getMetricIdHash(metricId, MetricIds.METRIC_TYPE_COUNTER);
         StatsExpressLog.write(StatsExpressLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
     }
 
@@ -60,7 +59,8 @@
      * @param amount to increment counter
      */
     public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
+        final long metricIdHash =
+                MetricIds.getMetricIdHash(metricId, MetricIds.METRIC_TYPE_COUNTER_WITH_UID);
         StatsExpressLog.write(
             StatsExpressLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
     }
diff --git a/java/com/android/modules/expresslog/Histogram.java b/java/com/android/modules/expresslog/Histogram.java
index be300bf..4f61c85 100644
--- a/java/com/android/modules/expresslog/Histogram.java
+++ b/java/com/android/modules/expresslog/Histogram.java
@@ -20,14 +20,12 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 
-import com.android.modules.expresslog.StatsExpressLog;
-
 import java.util.Arrays;
 
 /** Histogram encapsulates StatsD write API calls */
 public final class Histogram {
 
-    private final long mMetricIdHash;
+    private final String mMetricId;
     private final BinOptions mBinOptions;
 
     /**
@@ -37,7 +35,7 @@
      * @param binOptions to calculate bin index for samples
      */
     public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
-        mMetricIdHash = Utils.hashString(metricId);
+        mMetricId = metricId;
         mBinOptions = binOptions;
     }
 
@@ -47,9 +45,10 @@
      * @param sample value
      */
     public void logSample(float sample) {
+        final long hash = MetricIds.getMetricIdHash(mMetricId, MetricIds.METRIC_TYPE_HISTOGRAM);
         final int binIndex = mBinOptions.getBinForSample(sample);
-        StatsExpressLog.write(StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
-                /*count*/ 1, binIndex);
+        StatsExpressLog.write(
+                StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, hash, /*count*/ 1, binIndex);
     }
 
     /**
@@ -59,9 +58,15 @@
      * @param sample value
      */
     public void logSampleWithUid(int uid, float sample) {
+        final long hash =
+                MetricIds.getMetricIdHash(mMetricId, MetricIds.METRIC_TYPE_HISTOGRAM_WITH_UID);
         final int binIndex = mBinOptions.getBinForSample(sample);
-        StatsExpressLog.write(StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
-                mMetricIdHash, /*count*/ 1, binIndex, uid);
+        StatsExpressLog.write(
+                StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
+                hash, /*count*/
+                1,
+                binIndex,
+                uid);
     }
 
     /** Used by Histogram to map data sample to corresponding bin */
diff --git a/java/com/android/modules/expresslog/Utils.java b/java/com/android/modules/expresslog/Utils.java
deleted file mode 100644
index fde90fc..0000000
--- a/java/com/android/modules/expresslog/Utils.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2023 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.modules.expresslog;
-
-final class Utils {
-    static native long hashString(String stringToHash);
-}
diff --git a/java/com/android/modules/utils/FastDataInput.java b/java/com/android/modules/utils/FastDataInput.java
index daa86d5..1437f80 100644
--- a/java/com/android/modules/utils/FastDataInput.java
+++ b/java/com/android/modules/utils/FastDataInput.java
@@ -207,6 +207,10 @@
 
             return s;
         } else {
+            if (ref >= mStringRefs.length) {
+                throw new IOException("Invalid interned string reference " + ref + " for "
+                        + mStringRefs.length + " interned strings");
+            }
             return mStringRefs[ref];
         }
     }
diff --git a/java/com/android/modules/utils/build/UnboundedSdkLevel.java b/java/com/android/modules/utils/build/UnboundedSdkLevel.java
index 48185d5..cc84172 100644
--- a/java/com/android/modules/utils/build/UnboundedSdkLevel.java
+++ b/java/com/android/modules/utils/build/UnboundedSdkLevel.java
@@ -17,6 +17,7 @@
 package com.android.modules.utils.build;
 
 import android.os.Build;
+import android.util.ArraySet;
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
@@ -50,10 +51,22 @@
     private static final SparseArray<Set<String>> PREVIOUS_CODENAMES = new SparseArray<>(4);
 
     static {
-        PREVIOUS_CODENAMES.put(29, Set.of("Q"));
-        PREVIOUS_CODENAMES.put(30, Set.of("Q", "R"));
-        PREVIOUS_CODENAMES.put(31, Set.of("Q", "R", "S"));
-        PREVIOUS_CODENAMES.put(32, Set.of("Q", "R", "S", "Sv2"));
+        PREVIOUS_CODENAMES.put(29, setOf("Q"));
+        PREVIOUS_CODENAMES.put(30, setOf("Q", "R"));
+        PREVIOUS_CODENAMES.put(31, setOf("Q", "R", "S"));
+        PREVIOUS_CODENAMES.put(32, setOf("Q", "R", "S", "Sv2"));
+    }
+
+    private static Set<String> setOf(String ... contents) {
+        if (SdkLevel.isAtLeastR()) {
+            return Set.of(contents);
+        }
+        // legacy code for Q
+        Set<String> set = new ArraySet(contents.length);
+        for (String codename : contents) {
+            set.add(codename);
+        }
+        return set;
     }
 
     private static final UnboundedSdkLevel sInstance =
diff --git a/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java b/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java
index 837c43b..2242ca0 100644
--- a/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java
+++ b/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java
@@ -22,12 +22,13 @@
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.modules.utils.testing.AbstractExtendedMockitoRule.AbstractBuilder;
+import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic;
+import com.android.modules.utils.testing.ExtendedMockitoRule.MockStaticClasses;
+import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic;
+import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStaticClasses;
 
-import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
-import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 import org.mockito.Mockito;
@@ -35,11 +36,21 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -52,14 +63,20 @@
 
     private static final String TAG = AbstractExtendedMockitoRule.class.getSimpleName();
 
+    private static final AnnotationFetcher<SpyStatic, SpyStaticClasses>
+        sSpyStaticAnnotationFetcher = new AnnotationFetcher<>(SpyStatic.class,
+                SpyStaticClasses.class, r -> r.value());
+    private static final AnnotationFetcher<MockStatic, MockStaticClasses>
+        sMockStaticAnnotationFetcher = new AnnotationFetcher<>(MockStatic.class,
+                MockStaticClasses.class, r -> r.value());
+
     private final Object mTestClassInstance;
     private final Strictness mStrictness;
     private @Nullable final MockitoFramework mMockitoFramework;
     private @Nullable final Runnable mAfterSessionFinishedCallback;
-    private final List<Class<?>> mMockedStaticClasses;
-    private final List<Class<?>> mSpiedStaticClasses;
+    private final Set<Class<?>> mMockedStaticClasses;
+    private final Set<Class<?>> mSpiedStaticClasses;
     private final List<StaticMockFixture> mStaticMockFixtures;
-    private final @Nullable SessionBuilderVisitor mSessionBuilderConfigurator;
     private final boolean mClearInlineMocks;
 
     private MockitoSession mMockitoSession;
@@ -70,7 +87,6 @@
         mMockitoFramework = builder.mMockitoFramework;
         mMockitoSession = builder.mMockitoSession;
         mAfterSessionFinishedCallback = builder.mAfterSessionFinishedCallback;
-        mSessionBuilderConfigurator = builder.mSessionBuilderConfigurator;
         mMockedStaticClasses = builder.mMockedStaticClasses;
         mSpiedStaticClasses = builder.mSpiedStaticClasses;
         mStaticMockFixtures = builder.mStaticMockFixtures == null ? Collections.emptyList()
@@ -80,40 +96,92 @@
                 + ", mockedStaticClasses=" + mMockedStaticClasses
                 + ", spiedStaticClasses=" + mSpiedStaticClasses
                 + ", staticMockFixtures=" + mStaticMockFixtures
-                + ", sessionBuilderConfigurator=" + mSessionBuilderConfigurator
                 + ", afterSessionFinishedCallback=" + mAfterSessionFinishedCallback
                 + ", mockitoFramework=" + mMockitoFramework
                 + ", mockitoSession=" + mMockitoSession
                 + ", clearInlineMocks=" + mClearInlineMocks);
     }
 
-    @Override
-    public Statement apply(Statement base, Description description) {
-        createMockitoSession(description);
-
-        return new TestWatcher() {
-            @Override
-            protected void succeeded(Description description) {
-                tearDown(description, /* e=*/ null);
-            }
-
-            @Override
-            protected void skipped(AssumptionViolatedException e, Description description) {
-                tearDown(description, e);
-            }
-
-            @Override
-            protected void failed(Throwable e, Description description) {
-                tearDown(description, e);
-            }
-        }.apply(base, description);
+    /**
+     * Gets the mocked static classes present in the given test.
+     *
+     * <p>By default, it returns the classes defined by {@link AbstractBuilder#mockStatic(Class)}
+     * plus the classes present in the {@link MockStatic} and {@link MockStaticClasses}
+     * annotations (presents in the test method, its class, or its superclasses).
+     */
+    protected Set<Class<?>> getMockedStaticClasses(Description description) {
+        Set<Class<?>> staticClasses = new HashSet<>(mMockedStaticClasses);
+        sMockStaticAnnotationFetcher.getAnnotations(description)
+                .forEach(a -> staticClasses.add(a.value()));
+        return Collections.unmodifiableSet(staticClasses);
     }
 
-    private void createMockitoSession(Description description) {
+    /**
+     * Gets the spied static classes present in the given test.
+     *
+     * <p>By default, it returns the classes defined by {@link AbstractBuilder#spyStatic(Class)}
+     * plus the classes present in the {@link SpyStatic} and {@link SpyStaticClasses}
+     * annotations (presents in the test method, its class, or its superclasses).
+     */
+    protected Set<Class<?>> getSpiedStaticClasses(Description description) {
+        Set<Class<?>> staticClasses = new HashSet<>(mSpiedStaticClasses);
+        sSpyStaticAnnotationFetcher.getAnnotations(description)
+                .forEach(a -> staticClasses.add(a.value()));
+        return Collections.unmodifiableSet(staticClasses);
+    }
+
+    /**
+     * Gets whether the rule should clear the inline mocks after the given test.
+     *
+     * <p>By default, it returns {@code} (unless the rule was built with
+     * {@link AbstractBuilder#dontClearInlineMocks()}, but subclasses can override to change the
+     * behavior (for example, to decide based on custom annotations).
+     */
+    protected boolean getClearInlineMethodsAtTheEnd(Description description) {
+        return mClearInlineMocks;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                createMockitoSession(base, description);
+                Throwable error = null;
+                try {
+                    // TODO(b/296937563): need to add unit tests that make sure the session is
+                    // always closed
+                    base.evaluate();
+                } catch (Throwable t) {
+                    error = t;
+                }
+                try {
+                    tearDown(description, error);
+                } catch (Throwable t) {
+                    if (error != null) {
+                        Log.e(TAG, "Teardown failed for " + description.getDisplayName()
+                            + ", but not throwing it because test also threw (" + error + ")", t);
+                    } else {
+                        error = t;
+                    }
+                }
+                if (error != null) {
+                    // TODO(b/296937563): ideally should also add unit tests to make sure the
+                    // test error is thrown (in case tearDown() above fails)
+                    throw error;
+                }
+            }
+        };
+    }
+
+    private void createMockitoSession(Statement base, Description description) {
+        // TODO(b/296937563): might be prudent to save the session statically so it's explicitly
+        // closed in case it fails to be created again if for some reason it was not closed by us
+        // (although that should not happen)
         Log.v(TAG, "Creating session builder with strictness " + mStrictness);
         StaticMockitoSessionBuilder mSessionBuilder = mockitoSession().strictness(mStrictness);
 
-        setUpMockedClasses(mSessionBuilder);
+        setUpMockedClasses(description, mSessionBuilder);
 
         if (mTestClassInstance != null) {
             Log.v(TAG, "Initializing mocks on " + description + " using " + mSessionBuilder);
@@ -132,25 +200,22 @@
         setUpMockBehaviors();
     }
 
-    private void setUpMockedClasses(StaticMockitoSessionBuilder sessionBuilder) {
+    private void setUpMockedClasses(Description description,
+            StaticMockitoSessionBuilder sessionBuilder) {
         if (!mStaticMockFixtures.isEmpty()) {
             for (StaticMockFixture fixture : mStaticMockFixtures) {
                 Log.v(TAG, "Calling setUpMockedClasses(" + sessionBuilder + ") on " + fixture);
                 fixture.setUpMockedClasses(sessionBuilder);
             }
         }
-        for (Class<?> clazz: mMockedStaticClasses) {
+        for (Class<?> clazz: getMockedStaticClasses(description)) {
             Log.v(TAG, "Calling mockStatic() on " + clazz);
             sessionBuilder.mockStatic(clazz);
         }
-        for (Class<?> clazz: mSpiedStaticClasses) {
+        for (Class<?> clazz: getSpiedStaticClasses(description)) {
             Log.v(TAG, "Calling spyStatic() on " + clazz);
             sessionBuilder.spyStatic(clazz);
         }
-        if (mSessionBuilderConfigurator != null) {
-            Log.v(TAG, "Visiting " + mSessionBuilderConfigurator + " with " + sessionBuilder);
-            mSessionBuilderConfigurator.visit(sessionBuilder);
-        }
     }
 
     private void setUpMockBehaviors() {
@@ -183,12 +248,13 @@
                 }
             }
         } finally {
-            clearInlineMocks();
+            clearInlineMocks(description);
         }
     }
 
-    private void clearInlineMocks() {
-        if (!mClearInlineMocks) {
+    private void clearInlineMocks(Description description) {
+        boolean clearIt = getClearInlineMethodsAtTheEnd(description);
+        if (!clearIt) {
             Log.d(TAG, "NOT calling clearInlineMocks() as set on builder");
             return;
         }
@@ -208,13 +274,12 @@
     public static abstract class AbstractBuilder<R extends
             AbstractExtendedMockitoRule<R, B>, B extends AbstractBuilder<R, B>> {
         final Object mTestClassInstance;
-        final List<Class<?>> mMockedStaticClasses = new ArrayList<>();
-        final List<Class<?>> mSpiedStaticClasses = new ArrayList<>();
+        final Set<Class<?>> mMockedStaticClasses = new HashSet<>();
+        final Set<Class<?>> mSpiedStaticClasses = new HashSet<>();
         @Nullable List<StaticMockFixture> mStaticMockFixtures;
         Strictness mStrictness = Strictness.LENIENT;
         @Nullable MockitoFramework mMockitoFramework;
         @Nullable MockitoSession mMockitoSession;
-        @Nullable SessionBuilderVisitor mSessionBuilderConfigurator;
         @Nullable Runnable mAfterSessionFinishedCallback;
         boolean mClearInlineMocks = true;
 
@@ -248,11 +313,9 @@
          * com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder#mockStatic(Class)}.
          *
          * @throws IllegalStateException if the same class was already passed to
-         *   {@link #mockStatic(Class)} or {@link #spyStatic(Class)} or if
-         *   {@link #configureSessionBuilder(SessionBuilderVisitor)} was called before.
+         *   {@link #mockStatic(Class)} or {@link #spyStatic(Class)}.
          */
         public final B mockStatic(Class<?> clazz) {
-            checkConfigureSessionBuilderNotCalled();
             mMockedStaticClasses.add(checkClassNotMockedOrSpied(clazz));
             return thisBuilder();
         }
@@ -262,11 +325,9 @@
          * com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder#spyStatic(Class)}.
          *
          * @throws IllegalStateException if the same class was already passed to
-         *   {@link #mockStatic(Class)} or {@link #spyStatic(Class)} or if
-         *   {@link #configureSessionBuilder(SessionBuilderVisitor)} was called before.
+         *   {@link #mockStatic(Class)} or {@link #spyStatic(Class)}.
          */
         public final B spyStatic(Class<?> clazz) {
-            checkConfigureSessionBuilderNotCalled();
             mSpiedStaticClasses.add(checkClassNotMockedOrSpied(clazz));
             return thisBuilder();
         }
@@ -288,27 +349,6 @@
             return thisBuilder();
         }
 
-        // TODO(b/281577492): remove once CachedAppOptimizerTest doesn't use anymore
-        /**
-         * Alternative for {@link #spyStatic(Class)} / {@link #mockStatic(Class)}; typically used
-         * when the same setup is shared by multiple tests.
-         *
-         * @deprecated use {@link #addStaticMockFixtures(Supplier...)} instead
-         *
-         * @throws IllegalStateException if {@link #mockStatic(Class)} or {@link #spyStatic(Class)}
-         * was called before.
-         */
-        @Deprecated
-        public final B configureSessionBuilder(
-                SessionBuilderVisitor sessionBuilderConfigurator) {
-            Preconditions.checkState(mMockedStaticClasses.isEmpty(),
-                    "mockStatic() already called");
-            Preconditions.checkState(mSpiedStaticClasses.isEmpty(),
-                    "spyStatic() already called");
-            mSessionBuilderConfigurator = Objects.requireNonNull(sessionBuilderConfigurator);
-            return thisBuilder();
-        }
-
         /**
          * Runs the given {@code runnable} after the session finished.
          *
@@ -354,29 +394,78 @@
             return (B) this;
         }
 
-        private void checkConfigureSessionBuilderNotCalled() {
-            Preconditions.checkState(mSessionBuilderConfigurator == null,
-                    "configureSessionBuilder() already called");
-        }
-
         private Class<?> checkClassNotMockedOrSpied(Class<?> clazz) {
             Objects.requireNonNull(clazz);
-            Preconditions.checkState(!mMockedStaticClasses.contains(clazz),
-                    "class %s already mocked", clazz);
-            Preconditions.checkState(!mSpiedStaticClasses.contains(clazz),
-                    "class %s already spied", clazz);
+            checkState(!mMockedStaticClasses.contains(clazz), "class %s already mocked", clazz);
+            checkState(!mSpiedStaticClasses.contains(clazz), "class %s already spied", clazz);
             return clazz;
         }
     }
 
-    /**
-     * Visitor for {@link StaticMockitoSessionBuilder}.
-     */
-    public interface SessionBuilderVisitor {
+    // Copied from com.android.internal.util.Preconditions, as that method is not available on RVC
+    private static void checkState(boolean expression, String messageTemplate,
+            Object... messageArgs) {
+        if (!expression) {
+            throw new IllegalStateException(String.format(messageTemplate, messageArgs));
+        }
+    }
 
-        /**
-         * Visits it.
-         */
-        void visit(StaticMockitoSessionBuilder builder);
+    // TODO: make it public so it can be used by other modules
+    private static final class AnnotationFetcher<A extends Annotation, R extends Annotation> {
+
+        private final Class<A> mAnnotationType;
+        private final Class<R> mRepeatableType;
+        private final Function<R, A[]> mConverter;
+
+        AnnotationFetcher(Class<A> annotationType, Class<R> repeatableType,
+                Function<R, A[]> converter) {
+            mAnnotationType = annotationType;
+            mRepeatableType = repeatableType;
+            mConverter = converter;
+        }
+
+        private void add(Set<A> allAnnotations, R repeatableAnnotation) {
+            A[] repeatedAnnotations = mConverter.apply(repeatableAnnotation);
+            for (A repeatedAnnotation : repeatedAnnotations) {
+                allAnnotations.add(repeatedAnnotation);
+            }
+        }
+
+        Set<A> getAnnotations(Description description) {
+            Set<A> allAnnotations = new HashSet<>();
+
+            // Gets the annotations from the method first
+            Collection<Annotation> annotations = description.getAnnotations();
+            if (annotations != null) {
+                for (Annotation annotation : annotations) {
+                    if (mAnnotationType.isInstance(annotation)) {
+                        allAnnotations.add(mAnnotationType.cast(annotation));
+                    }
+                    if (mRepeatableType.isInstance(annotation)) {
+                        add(allAnnotations, mRepeatableType.cast(annotation));
+                    }
+                }
+            }
+
+            // Then superclasses
+            Class<?> clazz = description.getTestClass();
+            do {
+                A[] repeatedAnnotations = clazz.getAnnotationsByType(mAnnotationType);
+                if (repeatedAnnotations != null) {
+                    for (A repeatedAnnotation : repeatedAnnotations) {
+                        allAnnotations.add(repeatedAnnotation);
+                    }
+                }
+                R[] repeatableAnnotations = clazz.getAnnotationsByType(mRepeatableType);
+                if (repeatableAnnotations != null) {
+                    for (R repeatableAnnotation : repeatableAnnotations) {
+                        add(allAnnotations, mRepeatableType.cast(repeatableAnnotation));
+                    }
+                }
+                clazz = clazz.getSuperclass();
+            } while (clazz != null);
+
+            return allAnnotations;
+        }
     }
 }
diff --git a/java/com/android/modules/utils/testing/ExtendedMockitoRule.java b/java/com/android/modules/utils/testing/ExtendedMockitoRule.java
index 8eeaddd..fa7d7fa 100644
--- a/java/com/android/modules/utils/testing/ExtendedMockitoRule.java
+++ b/java/com/android/modules/utils/testing/ExtendedMockitoRule.java
@@ -18,6 +18,12 @@
 import com.android.modules.utils.testing.AbstractExtendedMockitoRule.AbstractBuilder;
 import com.android.modules.utils.testing.ExtendedMockitoRule.Builder;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
  * Rule to make it easier to use Extended Mockito:
  *
@@ -71,4 +77,30 @@
             return new ExtendedMockitoRule(this);
         }
     }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD, ElementType.TYPE})
+    @Repeatable(SpyStaticClasses.class)
+    public @interface SpyStatic {
+        Class<?> value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD, ElementType.TYPE})
+    public @interface SpyStaticClasses {
+        SpyStatic[] value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD, ElementType.TYPE})
+    @Repeatable(MockStaticClasses.class)
+    public @interface MockStatic {
+        Class<?> value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD, ElementType.TYPE})
+    public @interface MockStaticClasses {
+        MockStatic[] value();
+    }
 }
diff --git a/javatests/com/android/modules/expresslog/Android.bp b/javatests/com/android/modules/expresslog/Android.bp
index dd52750..9396e17 100644
--- a/javatests/com/android/modules/expresslog/Android.bp
+++ b/javatests/com/android/modules/expresslog/Android.bp
@@ -37,40 +37,7 @@
         "android.test.runner",
     ],
 
-    jni_libs: [
-        "libexpresslog_test_jni",
-    ],
-
     test_suites: [
         "general-tests",
     ],
 }
-
-cc_library_shared {
-    name: "libexpresslog_test_jni",
-
-    sdk_version: "current",
-    min_sdk_version: "30",
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wextra",
-
-        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
-    ],
-    srcs: [
-        "jni/onload.cpp",
-    ],
-    header_libs: [
-        "liblog_headers",
-        "libnativehelper_header_only",
-    ],
-    shared_libs: [
-        "liblog",
-    ],
-    static_libs: [
-        "libexpresslog_jni",
-        "libtextclassifier_hash_static",
-    ],
-}
diff --git a/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java b/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java
index 8defce7..1c42788 100644
--- a/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java
+++ b/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java
@@ -27,10 +27,6 @@
 @RunWith(JUnit4.class)
 @SmallTest
 public class ScaledRangeOptionsTest {
-    static {
-        System.loadLibrary("expresslog_test_jni");
-    }
-
     private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
 
     @Test
diff --git a/javatests/com/android/modules/expresslog/UniformOptionsTest.java b/javatests/com/android/modules/expresslog/UniformOptionsTest.java
index 3cc03ec..cad4c3f 100644
--- a/javatests/com/android/modules/expresslog/UniformOptionsTest.java
+++ b/javatests/com/android/modules/expresslog/UniformOptionsTest.java
@@ -26,10 +26,6 @@
 @RunWith(JUnit4.class)
 @SmallTest
 public class UniformOptionsTest {
-    static {
-        System.loadLibrary("expresslog_test_jni");
-    }
-
     private static final String TAG = UniformOptionsTest.class.getSimpleName();
 
     @Test
diff --git a/javatests/com/android/modules/expresslog/jni/.clang-format b/javatests/com/android/modules/expresslog/jni/.clang-format
deleted file mode 100644
index cead3a0..0000000
--- a/javatests/com/android/modules/expresslog/jni/.clang-format
+++ /dev/null
@@ -1,17 +0,0 @@
-BasedOnStyle: Google
-AllowShortIfStatementsOnASingleLine: true
-AllowShortFunctionsOnASingleLine: false
-AllowShortLoopsOnASingleLine: true
-BinPackArguments: true
-BinPackParameters: true
-ColumnLimit: 100
-CommentPragmas: NOLINT:.*
-ContinuationIndentWidth: 8
-DerivePointerAlignment: false
-IndentWidth: 4
-PointerAlignment: Left
-TabWidth: 4
-AccessModifierOffset: -4
-IncludeCategories:
-  - Regex:    '^"Log\.h"'
-    Priority:    -1
diff --git a/javatests/com/android/modules/expresslog/jni/onload.cpp b/javatests/com/android/modules/expresslog/jni/onload.cpp
deleted file mode 100644
index a112467..0000000
--- a/javatests/com/android/modules/expresslog/jni/onload.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-#define LOG_TAG "TeX"
-
-#include <jni.h>
-#include <log/log.h>
-
-namespace android {
-extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
-}  // namespace android
-
-using namespace android;
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
-    JNIEnv* env = NULL;
-    jint result = -1;
-
-    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
-        ALOGE("GetEnv failed!");
-        return result;
-    }
-    ALOG_ASSERT(env, "Could not retrieve the env!");
-
-    register_com_android_modules_expresslog_Utils(env);
-    return JNI_VERSION_1_4;
-}
diff --git a/javatests/com/android/modules/utils/build/Android.bp b/javatests/com/android/modules/utils/build/Android.bp
index 239530a..06ceb34 100644
--- a/javatests/com/android/modules/utils/build/Android.bp
+++ b/javatests/com/android/modules/utils/build/Android.bp
@@ -25,7 +25,7 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "modules-utils-build",
-        "truth-prebuilt",
+        "truth",
     ],
     test_suites: ["general-tests"],
 }
diff --git a/javatests/com/android/modules/utils/testing/Android.bp b/javatests/com/android/modules/utils/testing/Android.bp
index 418239a..5da571b 100644
--- a/javatests/com/android/modules/utils/testing/Android.bp
+++ b/javatests/com/android/modules/utils/testing/Android.bp
@@ -34,7 +34,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "platform-test-annotations",
-        "truth-prebuilt",
+        "truth",
     ],
 
     libs: [
diff --git a/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java b/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java
index cb54f9f..26d0cc3 100644
--- a/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java
+++ b/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java
@@ -31,6 +31,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic;
+import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic;
+
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
@@ -46,20 +49,27 @@
 import org.mockito.plugins.MockitoPlugins;
 import org.mockito.quality.Strictness;
 
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
 import java.util.function.Supplier;
 
 @RunWith(MockitoJUnitRunner.class)
 public final class ExtendedMockitoRuleTest {
+
     public static final String TAG = ExtendedMockitoRuleTest.class.getSimpleName();
 
+    // Not a real test (i.e., it doesn't exist on this class), but it's passed to Description
+    private static final String TEST_METHOD_BEING_EXECUTED = "testAmI..OrNot";
+
     private @Mock Statement mStatement;
-    private @Mock Description mDescription;
     private @Mock Runnable mRunnable;
-    private @Mock ExtendedMockitoRule.SessionBuilderVisitor mSessionBuilderVisitor;
     private @Mock StaticMockFixture mStaticMockFixture1;
     private @Mock StaticMockFixture mStaticMockFixture2;
     private @Mock StaticMockFixture mStaticMockFixture3;
 
+    private final Description mDescription = newTestMethod();
     private final ClassUnderTest mClassUnderTest = new ClassUnderTest();
     private final ExtendedMockitoRule.Builder mBuilder =
             new ExtendedMockitoRule.Builder(mClassUnderTest);
@@ -89,12 +99,6 @@
     }
 
     @Test
-    public void testBuilder_configureSessionBuilder_null() {
-        assertThrows(NullPointerException.class,
-                () -> mBuilder.configureSessionBuilder(null));
-    }
-
-    @Test
     public void testBuilder_mockStatic_null() {
         assertThrows(NullPointerException.class, () -> mBuilder.mockStatic(null));
     }
@@ -164,7 +168,8 @@
 
     @Test
     public void testMocksStatic() throws Throwable {
-        mBuilder.mockStatic(StaticClass.class).build().apply(new Statement() {
+        ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class).build();
+        rule.apply(new Statement() {
             @Override
             public void evaluate() throws Throwable {
                 doReturn("mocko()").when(() -> StaticClass.marco());
@@ -175,6 +180,12 @@
                         .that(StaticClass.water()).isNull(); // not mocked
             }
         }, mDescription).evaluate();
+
+        Set<Class<?>> mockedClasses = rule.getMockedStaticClasses(mDescription);
+        assertWithMessage("rule.getMockedStaticClasses()").that(mockedClasses)
+                .containsExactly(StaticClass.class);
+        assertThrows(RuntimeException.class,
+                () -> mockedClasses.add(ExtendedMockitoRuleTest.class));
     }
 
     @Test
@@ -186,29 +197,79 @@
 
     @Test
     public void testMocksStatic_multipleClasses() throws Throwable {
-        mBuilder.mockStatic(StaticClass.class).mockStatic(AnotherStaticClass.class).build().apply(
-                new Statement() {
-                    @Override
-                    public void evaluate() throws Throwable {
-                        doReturn("mocko()").when(() -> StaticClass.marco());
-                        doReturn("MOCKO()").when(() -> AnotherStaticClass.marco());
+        ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class)
+                .mockStatic(AnotherStaticClass.class).build();
+        rule.apply(new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                doReturn("mocko()").when(() -> StaticClass.marco());
+                doReturn("MOCKO()").when(() -> AnotherStaticClass.marco());
 
-                        assertWithMessage("StaticClass.marco()")
-                                .that(StaticClass.marco()).isEqualTo("mocko()");
-                        assertWithMessage("StaticClass.water()")
-                                .that(StaticClass.water()).isNull(); // not mocked
+                assertWithMessage("StaticClass.marco()")
+                        .that(StaticClass.marco()).isEqualTo("mocko()");
+                assertWithMessage("StaticClass.water()")
+                        .that(StaticClass.water()).isNull(); // not mocked
 
-                        assertWithMessage("AnotherStaticClass.marco()")
-                                .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()");
-                        assertWithMessage("AnotherStaticClass.water()")
-                                .that(AnotherStaticClass.water()).isNull(); // not mocked
-                    }
-                }, mDescription).evaluate();
+                assertWithMessage("AnotherStaticClass.marco()")
+                        .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()");
+                assertWithMessage("AnotherStaticClass.water()")
+                        .that(AnotherStaticClass.water()).isNull(); // not mocked
+            }
+        }, mDescription).evaluate();
+
+        Set<Class<?>> mockedClasses = rule.getMockedStaticClasses(mDescription);
+        assertWithMessage("rule.getMockedStaticClasses()").that(mockedClasses)
+                .containsExactly(StaticClass.class, AnotherStaticClass.class);
+        assertThrows(RuntimeException.class,
+                () -> mockedClasses.add(ExtendedMockitoRuleTest.class));
+    }
+
+    @Test
+    public void testMockStatic_ruleAndAnnotation() throws Throwable {
+        ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class).build();
+
+        rule.apply(new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                doReturn("mocko()").when(() -> StaticClass.marco());
+                doReturn("MOCKO()").when(() -> AnotherStaticClass.marco());
+
+                assertWithMessage("StaticClass.marco()")
+                        .that(StaticClass.marco()).isEqualTo("mocko()");
+                assertWithMessage("StaticClass.water()")
+                        .that(StaticClass.water()).isNull(); // not mocked
+
+                assertWithMessage("AnotherStaticClass.marco()")
+                        .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()");
+                assertWithMessage("AnotherStaticClass.water()")
+                        .that(AnotherStaticClass.water()).isNull(); // not mocked
+            }
+        }, newTestMethod(new MockStaticAnnotation(AnotherStaticClass.class))).evaluate();
+    }
+
+    // Ideally, we should test the annotations indirectly (i.e., by asserting their static classes
+    // are properly mocked, but pragmatically speaking, testing the getSpiedStatic() is enough - and
+    // much simpler
+    @Test
+    public void testMockStatic_fromEverywhere() throws Throwable {
+        ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class).build();
+
+        Set<Class<?>> mockedClasses = rule.getMockedStaticClasses(newTestMethod(SubClass.class,
+                new MockStaticAnnotation(AnotherStaticClass.class)));
+
+        assertWithMessage("rule.getMockedStaticClasses()").that(mockedClasses).containsExactly(
+                StaticClass.class, AnotherStaticClass.class, StaticClassMockedBySuperClass.class,
+                AnotherStaticClassMockedBySuperClass.class, StaticClassMockedBySubClass.class,
+                AnotherStaticClassMockedBySubClass.class);
+        assertThrows(RuntimeException.class,
+                () -> mockedClasses.add(ExtendedMockitoRuleTest.class));
     }
 
     @Test
     public void testSpyStatic() throws Throwable {
-        mBuilder.spyStatic(StaticClass.class).build().apply(new Statement() {
+        ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class).build();
+
+        rule.apply(new Statement() {
             @Override
             public void evaluate() throws Throwable {
                 doReturn("mocko()").when(() -> StaticClass.marco());
@@ -219,6 +280,11 @@
                         .that(StaticClass.water()).isEqualTo("polo");
             }
         }, mDescription).evaluate();
+
+        Set<Class<?>> spiedClasses = rule.getSpiedStaticClasses(mDescription);
+        assertWithMessage("rule.getSpiedStaticClasses()").that(spiedClasses)
+                .containsExactly(StaticClass.class);
+        assertThrows(RuntimeException.class, () -> spiedClasses.add(ExtendedMockitoRuleTest.class));
     }
 
     @Test
@@ -230,8 +296,10 @@
 
     @Test
     public void testSpyStatic_multipleClasses() throws Throwable {
-        mBuilder.spyStatic(StaticClass.class).spyStatic(AnotherStaticClass.class).build()
-                .apply(new Statement() {
+        ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class)
+                .spyStatic(AnotherStaticClass.class).build();
+
+        rule.apply(new Statement() {
                     @Override
                     public void evaluate() throws Throwable {
                         doReturn("mocko()").when(() -> StaticClass.marco());
@@ -248,12 +316,58 @@
                                 .that(AnotherStaticClass.water()).isEqualTo("POLO");
                     }
                 }, mDescription).evaluate();
+
+        Set<Class<?>> spiedClasses = rule.getSpiedStaticClasses(mDescription);
+        assertWithMessage("rule.getSpiedStaticClasses()").that(spiedClasses)
+                .containsExactly(StaticClass.class, AnotherStaticClass.class);
+        assertThrows(RuntimeException.class, () -> spiedClasses.add(ExtendedMockitoRuleTest.class));
+    }
+
+    @Test
+    public void testSpyStatic_ruleAndAnnotation() throws Throwable {
+        ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class).build();
+        rule.apply(new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                doReturn("mocko()").when(() -> StaticClass.marco());
+                doReturn("MOCKO()").when(() -> AnotherStaticClass.marco());
+
+                assertWithMessage("StaticClass.marco()")
+                        .that(StaticClass.marco()).isEqualTo("mocko()");
+                assertWithMessage("StaticClass.water()")
+                        .that(StaticClass.water()).isEqualTo("polo");
+
+                assertWithMessage("AnotherStaticClass.marco()")
+                        .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()");
+                assertWithMessage("AnotherStaticClass.water()")
+                        .that(AnotherStaticClass.water()).isEqualTo("POLO");
+            }
+        }, newTestMethod(new SpyStaticAnnotation(AnotherStaticClass.class))).evaluate();
+    }
+
+    // Ideally, we should test the annotations indirectly (i.e., by asserting their static classes
+    // are properly spied, but pragmatically speaking, testing the getSpiedStatic() is enough - and
+    // much simpler
+    @Test
+    public void testSpyStatic_fromEverywhere() throws Throwable {
+        ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class).build();
+
+        Set<Class<?>> spiedClasses = rule.getSpiedStaticClasses(newTestMethod(SubClass.class,
+                new SpyStaticAnnotation(AnotherStaticClass.class)));
+
+        assertWithMessage("rule.getSpiedStaticClasses()").that(spiedClasses).containsExactly(
+                StaticClass.class, AnotherStaticClass.class, StaticClassSpiedBySuperClass.class,
+                AnotherStaticClassSpiedBySuperClass.class, StaticClassSpiedBySubClass.class,
+                AnotherStaticClassSpiedBySubClass.class);
+        assertThrows(RuntimeException.class, () -> spiedClasses.add(ExtendedMockitoRuleTest.class));
     }
 
     @Test
     public void testMockAndSpyStatic() throws Throwable {
-        mBuilder.mockStatic(StaticClass.class).spyStatic(AnotherStaticClass.class).build()
-                .apply(new Statement() {
+        ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class)
+                .spyStatic(AnotherStaticClass.class).build();
+
+        rule.apply(new Statement() {
                     @Override
                     public void evaluate() throws Throwable {
                         doReturn("mocko()").when(() -> StaticClass.marco());
@@ -270,6 +384,18 @@
                                 .that(AnotherStaticClass.water()).isEqualTo("POLO");
                     }
                 }, mDescription).evaluate();
+
+        Set<Class<?>> spiedStaticClasses = rule.getSpiedStaticClasses(mDescription);
+        assertWithMessage("rule.getSpiedStaticClasses()").that(spiedStaticClasses)
+                .containsExactly(AnotherStaticClass.class);
+        assertThrows(RuntimeException.class,
+                () -> spiedStaticClasses.add(ExtendedMockitoRuleTest.class));
+
+        Set<Class<?>> mockedStaticClasses = rule.getMockedStaticClasses(mDescription);
+        assertWithMessage("rule.getMockedStaticClasses()").that(mockedStaticClasses)
+                .containsExactly(StaticClass.class);
+        assertThrows(RuntimeException.class,
+                () -> mockedStaticClasses.add(ExtendedMockitoRuleTest.class));
     }
 
     @Test
@@ -287,18 +413,6 @@
     }
 
     @Test
-    public void testSpyStatic_afterConfigureSessionBuilder() throws Throwable {
-        assertThrows(IllegalStateException.class, () -> mBuilder
-                .configureSessionBuilder(mSessionBuilderVisitor).spyStatic(StaticClass.class));
-    }
-
-    @Test
-    public void testMockStatic_afterConfigureSessionBuilder() throws Throwable {
-        assertThrows(IllegalStateException.class, () -> mBuilder
-                .configureSessionBuilder(mSessionBuilderVisitor).mockStatic(StaticClass.class));
-    }
-
-    @Test
     public void testAddStaticMockFixtures_once() throws Throwable {
         InOrder inOrder = inOrder(mStaticMockFixture1, mStaticMockFixture2);
 
@@ -382,26 +496,6 @@
     }
 
     @Test
-    public void testConfigureSessionBuilder_afterMockStatic() throws Throwable {
-        assertThrows(IllegalStateException.class, () -> mBuilder.mockStatic(StaticClass.class)
-                .configureSessionBuilder(mSessionBuilderVisitor));
-    }
-
-    @Test
-    public void testConfigureSessionBuilder_afterSpyStatic() throws Throwable {
-        assertThrows(IllegalStateException.class, () -> mBuilder.spyStatic(StaticClass.class)
-                .configureSessionBuilder(mSessionBuilderVisitor));
-    }
-
-    @Test
-    public void testConfigureSessionBuilder() throws Throwable {
-        mUnsafeBuilder.configureSessionBuilder(mSessionBuilderVisitor)
-                .build().apply(mStatement, mDescription).evaluate();
-
-        verify(mSessionBuilderVisitor).visit(notNull());
-    }
-
-    @Test
     public void testAfterSessionFinished() throws Throwable {
         mUnsafeBuilder.afterSessionFinished(mRunnable).build().apply(mStatement, mDescription)
                 .evaluate();
@@ -506,6 +600,16 @@
         assertWithMessage("mockito framework cleared").that(mockitoFramework.called).isTrue();
     }
 
+    @Test
+    public void testGetClearInlineMethodsAtTheEnd() throws Throwable {
+        assertWithMessage("getClearInlineMethodsAtTheEnd() by default")
+                .that(mBuilder.build().getClearInlineMethodsAtTheEnd(mDescription)).isTrue();
+        assertWithMessage("getClearInlineMethodsAtTheEnd() when built with dontClearInlineMocks()")
+                .that(mBuilder.dontClearInlineMocks().build()
+                        .getClearInlineMethodsAtTheEnd(mDescription))
+                .isFalse();
+    }
+
     private void applyRuleOnTestThatDoesntUseExpectation(@Nullable Strictness strictness)
             throws Throwable {
         Log.d(TAG, "applyRuleOnTestThatDoesntUseExpectation(): strictness= " + strictness);
@@ -520,6 +624,15 @@
         }, mDescription).evaluate();
     }
 
+    private static Description newTestMethod(Annotation... annotations) {
+        return newTestMethod(ClassUnderTest.class, annotations);
+    }
+
+    private static Description newTestMethod(Class<?> testClass, Annotation... annotations) {
+        return Description.createTestDescription(testClass, TEST_METHOD_BEING_EXECUTED,
+                annotations);
+    }
+
     private static final class ClassUnderTest {
         @Mock
         public DumbObject mMock;
@@ -622,4 +735,103 @@
             throw e;
         }
     }
+
+    private abstract static class ClassAnnotation<A extends Annotation> implements Annotation {
+        private Class<A> mAnnotationType;
+        private Class<?> mClass;
+
+        private ClassAnnotation(Class<A> annotationType, Class<?> clazz) {
+            mAnnotationType = annotationType;
+            mClass = clazz;
+        }
+
+        @Override
+        public final Class<A> annotationType() {
+            return mAnnotationType;
+        }
+
+        public final Class<?> value() {
+            return mClass;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mAnnotationType, mClass);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            ClassAnnotation other = (ClassAnnotation) obj;
+            return Objects.equals(mAnnotationType, other.mAnnotationType)
+                    && Objects.equals(mClass, other.mClass);
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "[" + mClass.getSimpleName() + "]";
+        }
+    }
+
+    private static final class SpyStaticAnnotation extends ClassAnnotation<SpyStatic>
+            implements SpyStatic {
+
+        private SpyStaticAnnotation(Class<?> clazz) {
+            super(SpyStatic.class, clazz);
+        }
+    }
+
+    private static final class MockStaticAnnotation extends ClassAnnotation<MockStatic>
+            implements MockStatic {
+
+        private MockStaticAnnotation(Class<?> clazz) {
+            super(MockStatic.class, clazz);
+        }
+    }
+
+    private static final class StaticClassMockedBySuperClass {
+    }
+
+    private static final class AnotherStaticClassMockedBySuperClass {
+    }
+    private static final class StaticClassSpiedBySuperClass {
+    }
+
+    private static final class AnotherStaticClassSpiedBySuperClass {
+    }
+
+    @SpyStatic(StaticClassSpiedBySuperClass.class)
+    @SpyStatic(AnotherStaticClassSpiedBySuperClass.class)
+    @MockStatic(StaticClassMockedBySuperClass.class)
+    @MockStatic(AnotherStaticClassMockedBySuperClass.class)
+    private static class SuperClass {
+
+    }
+
+    private static final class StaticClassMockedBySubClass {
+    }
+
+    private static final class AnotherStaticClassMockedBySubClass {
+    }
+
+    private static final class StaticClassSpiedBySubClass {
+    }
+
+    private static final class AnotherStaticClassSpiedBySubClass {
+    }
+
+    @SpyStatic(StaticClassSpiedBySubClass.class)
+    @SpyStatic(AnotherStaticClassSpiedBySubClass.class)
+    @MockStatic(StaticClassMockedBySubClass.class)
+    @MockStatic(AnotherStaticClassMockedBySubClass.class)
+    private static final class SubClass extends SuperClass{
+    }
 }
\ No newline at end of file
diff --git a/jni/expresslog/.clang-format b/jni/expresslog/.clang-format
deleted file mode 100644
index cead3a0..0000000
--- a/jni/expresslog/.clang-format
+++ /dev/null
@@ -1,17 +0,0 @@
-BasedOnStyle: Google
-AllowShortIfStatementsOnASingleLine: true
-AllowShortFunctionsOnASingleLine: false
-AllowShortLoopsOnASingleLine: true
-BinPackArguments: true
-BinPackParameters: true
-ColumnLimit: 100
-CommentPragmas: NOLINT:.*
-ContinuationIndentWidth: 8
-DerivePointerAlignment: false
-IndentWidth: 4
-PointerAlignment: Left
-TabWidth: 4
-AccessModifierOffset: -4
-IncludeCategories:
-  - Regex:    '^"Log\.h"'
-    Priority:    -1
diff --git a/jni/expresslog/Android.bp b/jni/expresslog/Android.bp
deleted file mode 100644
index 7bef576..0000000
--- a/jni/expresslog/Android.bp
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-// Copyright (C) 2023 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.
-
-// JNI library for Utils.hashString
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_static {
-    name: "libexpresslog_jni",
-
-    sdk_version: "current",
-    min_sdk_version: "30",
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wextra",
-
-        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
-    ],
-    srcs: [
-        "com_android_modules_expresslog_Utils.cpp",
-    ],
-    header_libs: [
-        "liblog_headers",
-        "libnativehelper_header_only",
-        "libtextclassifier_hash_headers",
-    ],
-    shared_libs: [
-        "liblog",
-    ],
-    static_libs: [
-        "libtextclassifier_hash_static",
-    ],
-    visibility: ["//visibility:public"],
-    apex_available: [
-        "//apex_available:anyapex",
-        "//apex_available:platform",
-    ],
-}
diff --git a/jni/expresslog/com_android_modules_expresslog_Utils.cpp b/jni/expresslog/com_android_modules_expresslog_Utils.cpp
deleted file mode 100644
index 973d946..0000000
--- a/jni/expresslog/com_android_modules_expresslog_Utils.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-#define LOG_NAMESPACE "TeX.tag."
-#define LOG_TAG "TeX"
-
-#include <log/log.h>
-#include <nativehelper/scoped_local_ref.h>
-#include <nativehelper/scoped_utf_chars.h>
-#include <utils/hash/farmhash.h>
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-static jclass gStringClass = nullptr;
-
-/**
- * Class:     com_android_modules_expresslog_Utils
- * Method:    hashString
- * Signature: (Ljava/lang/String;)J
- */
-static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
-    ScopedUtfChars name(env, metricNameObj);
-    if (name.c_str() == nullptr) {
-        return 0;
-    }
-
-    return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
-}
-
-static const JNINativeMethod gMethods[] = {
-        {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
-};
-
-namespace android {
-
-int register_com_android_modules_expresslog_Utils(JNIEnv* env) {
-    static const char* const kUtilsClassName = "com/android/modules/expresslog/Utils";
-    static const char* const kStringClassName = "java/lang/String";
-
-    ScopedLocalRef<jclass> utilsCls(env, env->FindClass(kUtilsClassName));
-    if (utilsCls.get() == nullptr) {
-        ALOGE("jni expresslog registration failure, class not found '%s'", kUtilsClassName);
-        return JNI_ERR;
-    }
-
-    jclass stringClass = env->FindClass(kStringClassName);
-    if (stringClass == nullptr) {
-        ALOGE("jni expresslog registration failure, class not found '%s'", kStringClassName);
-        return JNI_ERR;
-    }
-    gStringClass = static_cast<jclass>(env->NewGlobalRef(stringClass));
-    if (gStringClass == nullptr) {
-        ALOGE("jni expresslog Unable to create global reference '%s'", kStringClassName);
-        return JNI_ERR;
-    }
-
-    const jint count = sizeof(gMethods) / sizeof(gMethods[0]);
-    int status = env->RegisterNatives(utilsCls.get(), gMethods, count);
-    if (status < 0) {
-        ALOGE("jni expresslog registration failure, status: %d", status);
-        return JNI_ERR;
-    }
-    return JNI_VERSION_1_4;
-}
-
-}  // namespace android