Revert^2 "Filter memory tombstone fields before DropBox logging"

This reverts commit 9f17a890d098d4c882f9f3b9c8932fddc1b935d8.

Reason for revert: Re-enable memory tombstone filtering after fixing the RavenWood issue
Test: atest FrameworksUtilTestsRavenwood:android.util.proto.ProtoFieldFilterTest

Change-Id: Ie0e791712252ced8555386bacf841ff90a02ed85
diff --git a/core/java/android/util/proto/ProtoFieldFilter.java b/core/java/android/util/proto/ProtoFieldFilter.java
new file mode 100644
index 0000000..c3ae106
--- /dev/null
+++ b/core/java/android/util/proto/ProtoFieldFilter.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2025 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.util.proto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.function.Predicate;
+
+/**
+ * A utility class that reads raw protobuf data from an InputStream
+ * and copies only those fields for which a given predicate returns true.
+ *
+ * <p>
+ * This is a low-level approach that does not fully decode fields
+ * (unless necessary to determine lengths). It simply:
+ * <ul>
+ *   <li>Parses each field's tag (varint for field number & wire type)</li>
+ *   <li>If {@code includeFn(fieldNumber) == true}, copies
+ *       the tag bytes and the field bytes directly to the output</li>
+ *   <li>Otherwise, skips that field in the input</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Because we do not re-encode, unknown or unrecognized fields are copied
+ * <i>verbatim</i> and remain exactly as in the input (useful for partial
+ * parsing or partial transformations).
+ * </p>
+ *
+ * <p>
+ * Note: This class only filters based on top-level field numbers. For length-delimited
+ * fields (including nested messages), the entire contents are either copied or skipped
+ * as a single unit. The class is not capable of nested filtering.
+ * </p>
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class ProtoFieldFilter {
+
+    private static final int BUFFER_SIZE_BYTES = 4096;
+
+    private final Predicate<Integer> mFieldPredicate;
+    // General-purpose buffer for reading proto fields and their data
+    private final byte[] mBuffer;
+    // Buffer specifically designated to hold varint values (max 10 bytes in protobuf encoding)
+    private final byte[] mVarIntBuffer = new byte[10];
+
+    /**
+    * Constructs a ProtoFieldFilter with a predicate that considers depth.
+    *
+    * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+    *                       included in the output.
+    * @param bufferSize The size of the internal buffer used for processing proto fields.
+    *                   Larger buffers may improve performance when processing large
+    *                   length-delimited fields.
+    */
+    public ProtoFieldFilter(Predicate<Integer> fieldPredicate, int bufferSize) {
+        this.mFieldPredicate = fieldPredicate;
+        this.mBuffer = new byte[bufferSize];
+    }
+
+    /**
+    * Constructs a ProtoFieldFilter with a predicate that considers depth and
+    * uses a default buffer size.
+    *
+    * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+    *                       included in the output.
+    */
+    public ProtoFieldFilter(Predicate<Integer> fieldPredicate) {
+        this(fieldPredicate, BUFFER_SIZE_BYTES);
+    }
+
+    /**
+     * Reads raw protobuf data from {@code in} and writes only those fields
+     * passing {@code includeFn} to {@code out}. The predicate is given
+     * (fieldNumber, wireType) for each encountered field.
+     *
+     * @param in        The input stream of protobuf data
+     * @param out       The output stream to which we write the filtered protobuf
+     * @throws IOException If reading or writing fails, or if the protobuf data is corrupted
+     */
+    public void filter(InputStream in, OutputStream out) throws IOException {
+        int tagBytesLength;
+        while ((tagBytesLength = readRawVarint(in)) > 0) {
+            // Parse the varint loaded in mVarIntBuffer, through readRawVarint
+            long tagVal = parseVarint(mVarIntBuffer, tagBytesLength);
+            int fieldNumber = (int) (tagVal >>> ProtoStream.FIELD_ID_SHIFT);
+            int wireType   = (int) (tagVal & ProtoStream.WIRE_TYPE_MASK);
+
+            if (fieldNumber == 0) {
+                break;
+            }
+            if (mFieldPredicate.test(fieldNumber)) {
+                out.write(mVarIntBuffer, 0, tagBytesLength);
+                copyFieldData(in, out, wireType);
+            } else {
+                skipFieldData(in, wireType);
+            }
+        }
+    }
+
+    /**
+     * Reads a varint (up to 10 bytes) from the stream as raw bytes
+     * and returns it in a byte array. If the stream is at EOF, returns null.
+     *
+     * @param in The input stream
+     * @return the size of the varint bytes moved to mVarIntBuffer
+     * @throws IOException If an error occurs, or if we detect a malformed varint
+     */
+    private int readRawVarint(InputStream in) throws IOException {
+        // We attempt to read 1 byte. If none available => null
+        int b = in.read();
+        if (b < 0) {
+            return 0;
+        }
+        int count = 0;
+        mVarIntBuffer[count++] = (byte) b;
+        // If the continuation bit is set, we continue
+        while ((b & 0x80) != 0) {
+            // read next byte
+            b = in.read();
+            // EOF
+            if (b < 0) {
+                throw new IOException("Malformed varint: reached EOF mid-varint");
+            }
+            // max 10 bytes for varint 64
+            if (count >= 10) {
+                throw new IOException("Malformed varint: too many bytes (max 10)");
+            }
+            mVarIntBuffer[count++] = (byte) b;
+        }
+        return count;
+    }
+
+    /**
+     * Parses a varint from the given raw bytes and returns it as a long.
+     *
+     * @param rawVarint The bytes representing the varint
+     * @param byteLength The number of bytes to read from rawVarint
+     * @return The decoded long value
+     */
+    private static long parseVarint(byte[] rawVarint, int byteLength) throws IOException {
+        long result = 0;
+        int shift = 0;
+        for (int i = 0; i < byteLength; i++) {
+            result |= ((rawVarint[i] & 0x7F) << shift);
+            shift += 7;
+            if (shift > 63) {
+                throw new IOException("Malformed varint: exceeds 64 bits");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Copies the wire data for a single field from {@code in} to {@code out},
+     * assuming we have already read the field's tag.
+     *
+     * @param in       The input stream (protobuf data)
+     * @param out      The output stream
+     * @param wireType The wire type (0=varint, 1=fixed64, 2=length-delim, 5=fixed32)
+     * @throws IOException if reading/writing fails or data is malformed
+     */
+    private void copyFieldData(InputStream in, OutputStream out, int wireType)
+            throws IOException {
+        switch (wireType) {
+            case ProtoStream.WIRE_TYPE_VARINT:
+                copyVarint(in, out);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED64:
+                copyFixed(in, out, 8);
+                break;
+            case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+                copyLengthDelimited(in, out);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED32:
+                copyFixed(in, out, 4);
+                break;
+            // case WIRE_TYPE_START_GROUP:
+                // Not Supported
+            // case WIRE_TYPE_END_GROUP:
+                // Not Supported
+            default:
+                // Error or unrecognized wire type
+                throw new IOException("Unknown or unsupported wire type: " + wireType);
+        }
+    }
+
+    /**
+     * Skips the wire data for a single field from {@code in},
+     * assuming the field's tag was already read.
+     */
+    private void skipFieldData(InputStream in, int wireType) throws IOException {
+        switch (wireType) {
+            case ProtoStream.WIRE_TYPE_VARINT:
+                skipVarint(in);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED64:
+                skipBytes(in, 8);
+                break;
+            case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+                skipLengthDelimited(in);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED32:
+                skipBytes(in, 4);
+                break;
+             // case WIRE_TYPE_START_GROUP:
+                // Not Supported
+            // case WIRE_TYPE_END_GROUP:
+                // Not Supported
+            default:
+                throw new IOException("Unknown or unsupported wire type: " + wireType);
+        }
+    }
+
+    /** Copies a varint (the field's value) from in to out. */
+    private static void copyVarint(InputStream in, OutputStream out) throws IOException {
+        while (true) {
+            int b = in.read();
+            if (b < 0) {
+                throw new IOException("EOF while copying varint");
+            }
+            out.write(b);
+            if ((b & 0x80) == 0) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Copies exactly {@code length} bytes from {@code in} to {@code out}.
+     */
+    private void copyFixed(InputStream in, OutputStream out,
+                int length) throws IOException {
+        int toRead = length;
+        while (toRead > 0) {
+            int chunk = Math.min(toRead, mBuffer.length);
+            int readCount = in.read(mBuffer, 0, chunk);
+            if (readCount < 0) {
+                throw new IOException("EOF while copying fixed" + (length * 8) + " field");
+            }
+            out.write(mBuffer, 0, readCount);
+            toRead -= readCount;
+        }
+    }
+
+    /** Copies a length-delimited field */
+    private void copyLengthDelimited(InputStream in,
+                    OutputStream out) throws IOException {
+        // 1) read length varint (and copy)
+        int lengthVarintLength = readRawVarint(in);
+        if (lengthVarintLength <= 0) {
+            throw new IOException("EOF reading length for length-delimited field");
+        }
+        out.write(mVarIntBuffer, 0, lengthVarintLength);
+
+        long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+        if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+            throw new IOException("Invalid length for length-delimited field: " + lengthVal);
+        }
+
+        // 2) copy that many bytes
+        copyFixed(in, out, (int) lengthVal);
+    }
+
+    /** Skips a varint in the input (does not write anything). */
+    private static void skipVarint(InputStream in) throws IOException {
+        int bytesSkipped = 0;
+        while (true) {
+            int b = in.read();
+            if (b < 0) {
+                throw new IOException("EOF while skipping varint");
+            }
+            if ((b & 0x80) == 0) {
+                break;
+            }
+            bytesSkipped++;
+            if (bytesSkipped > 10) {
+                throw new IOException("Malformed varint: exceeds maximum length of 10 bytes");
+            }
+        }
+    }
+
+    /** Skips exactly n bytes. */
+    private void skipBytes(InputStream in, long n) throws IOException {
+        long skipped = in.skip(n);
+        // If skip fails, fallback to reading the remaining bytes
+        if (skipped < n) {
+            long bytesRemaining = n - skipped;
+
+            while (bytesRemaining > 0) {
+                int bytesToRead = (int) Math.min(bytesRemaining, mBuffer.length);
+                int bytesRead = in.read(mBuffer, 0, bytesToRead);
+                if (bytesRemaining < 0) {
+                    throw new IOException("EOF while skipping bytes");
+                }
+                bytesRemaining -= bytesRead;
+            }
+        }
+    }
+
+    /**
+     * Skips a length-delimited field.
+     * 1) read the length as varint,
+     * 2) skip that many bytes
+     */
+    private void skipLengthDelimited(InputStream in) throws IOException {
+        int lengthVarintLength = readRawVarint(in);
+        if (lengthVarintLength <= 0) {
+            throw new IOException("EOF reading length for length-delimited field");
+        }
+        long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+        if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+            throw new IOException("Invalid length to skip: " + lengthVal);
+        }
+        skipBytes(in, lengthVal);
+    }
+
+}
diff --git a/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
new file mode 100644
index 0000000..76d0aaa
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2025 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.util.proto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Unit tests for {@link android.util.proto.ProtoFieldFilter}.
+ *
+ *  Build/Install/Run:
+ *  atest FrameworksCoreTests:ProtoFieldFilterTest
+ *
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProtoFieldFilterTest {
+
+    private static final class FieldTypes {
+        static final long INT64 = ProtoStream.FIELD_TYPE_INT64 | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long FIXED64 = ProtoStream.FIELD_TYPE_FIXED64 | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long BYTES = ProtoStream.FIELD_TYPE_BYTES | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long FIXED32 = ProtoStream.FIELD_TYPE_FIXED32 | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long MESSAGE = ProtoStream.FIELD_TYPE_MESSAGE | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long INT32 = ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_SINGLE;
+    }
+
+    private static ProtoOutputStream createBasicTestProto() {
+        ProtoOutputStream out = new ProtoOutputStream();
+
+        out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+        out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+        out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{1, 2, 3, 4, 5});
+        out.writeFixed32(ProtoStream.makeFieldId(4, FieldTypes.FIXED32), 0xDEADBEEF);
+
+        return out;
+    }
+
+    private static byte[] filterProto(byte[] input, ProtoFieldFilter filter) throws IOException {
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        filter.filter(inputStream, outputStream);
+        return outputStream.toByteArray();
+    }
+
+    @Test
+    public void testNoFieldsFiltered() throws IOException {
+        byte[] input = createBasicTestProto().getBytes();
+        byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+        assertArrayEquals("No fields should be filtered out", input, output);
+    }
+
+    @Test
+    public void testAllFieldsFiltered() throws IOException {
+        byte[] input = createBasicTestProto().getBytes();
+        byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> false));
+
+        assertEquals("All fields should be filtered out", 0, output.length);
+    }
+
+    @Test
+    public void testSpecificFieldsFiltered() throws IOException {
+
+        ProtoOutputStream out = createBasicTestProto();
+        byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+        ProtoInputStream in = new ProtoInputStream(output);
+        boolean[] fieldsFound = new boolean[5];
+
+        int fieldNumber;
+        while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+            fieldsFound[fieldNumber] = true;
+            switch (fieldNumber) {
+                case 1:
+                    assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+                    break;
+                case 2:
+                    fail("Field 2 should be filtered out");
+                    break;
+                case 3:
+                    assertArrayEquals(new byte[]{1, 2, 3, 4, 5},
+                            in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+                    break;
+                case 4:
+                    assertEquals(0xDEADBEEF,
+                            in.readInt(ProtoStream.makeFieldId(4, FieldTypes.FIXED32)));
+                    break;
+                default:
+                    fail("Unexpected field number: " + fieldNumber);
+            }
+        }
+
+        assertTrue("Field 1 should be present", fieldsFound[1]);
+        assertFalse("Field 2 should be filtered", fieldsFound[2]);
+        assertTrue("Field 3 should be present", fieldsFound[3]);
+        assertTrue("Field 4 should be present", fieldsFound[4]);
+    }
+
+    @Test
+    public void testDifferentWireTypes() throws IOException {
+        ProtoOutputStream out = new ProtoOutputStream();
+
+        out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+        out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+        out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{10, 20, 30});
+
+        long token = out.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+        out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 42);
+        out.end(token);
+
+        out.writeFixed32(ProtoStream.makeFieldId(5, FieldTypes.FIXED32), 0xDEADBEEF);
+
+        byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(fieldNumber -> true));
+
+        ProtoInputStream in = new ProtoInputStream(output);
+        boolean[] fieldsFound = new boolean[6];
+
+        int fieldNumber;
+        while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+            fieldsFound[fieldNumber] = true;
+            switch (fieldNumber) {
+                case 1:
+                    assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+                    break;
+                case 2:
+                    assertEquals(0x1234567890ABCDEFL,
+                            in.readLong(ProtoStream.makeFieldId(2, FieldTypes.FIXED64)));
+                    break;
+                case 3:
+                    assertArrayEquals(new byte[]{10, 20, 30},
+                            in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+                    break;
+                case 4:
+                    token = in.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+                    assertTrue(in.nextField() == 1);
+                    assertEquals(42, in.readInt(ProtoStream.makeFieldId(1, FieldTypes.INT32)));
+                    assertTrue(in.nextField() == ProtoInputStream.NO_MORE_FIELDS);
+                    in.end(token);
+                    break;
+                case 5:
+                    assertEquals(0xDEADBEEF,
+                            in.readInt(ProtoStream.makeFieldId(5, FieldTypes.FIXED32)));
+                    break;
+                default:
+                    fail("Unexpected field number: " + fieldNumber);
+            }
+        }
+
+        assertTrue("All fields should be present",
+                fieldsFound[1] && fieldsFound[2] && fieldsFound[3]
+                && fieldsFound[4] && fieldsFound[5]);
+    }
+    @Test
+    public void testNestedMessagesUnfiltered() throws IOException {
+        ProtoOutputStream out = new ProtoOutputStream();
+
+        out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+
+        long token = out.start(ProtoStream.makeFieldId(2, FieldTypes.MESSAGE));
+        out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 6789);
+        out.writeFixed32(ProtoStream.makeFieldId(2, FieldTypes.FIXED32), 0xCAFEBABE);
+        out.end(token);
+
+        byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+        // Verify output
+        ProtoInputStream in = new ProtoInputStream(output);
+        boolean[] fieldsFound = new boolean[3];
+
+        int fieldNumber;
+        while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+            fieldsFound[fieldNumber] = true;
+            if (fieldNumber == 1) {
+                assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+            } else {
+                fail("Unexpected field number: " + fieldNumber);
+            }
+        }
+
+        assertTrue("Field 1 should be present", fieldsFound[1]);
+        assertFalse("Field 2 should be filtered out", fieldsFound[2]);
+    }
+
+    @Test
+    public void testRepeatedFields() throws IOException {
+
+        ProtoOutputStream out = new ProtoOutputStream();
+        long fieldId = ProtoStream.makeFieldId(1,
+                ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_REPEATED);
+
+        out.writeRepeatedInt32(fieldId, 100);
+        out.writeRepeatedInt32(fieldId, 200);
+        out.writeRepeatedInt32(fieldId, 300);
+
+        byte[] input = out.getBytes();
+
+        byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+
+        assertArrayEquals("Repeated fields should be preserved", input, output);
+    }
+
+}
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index f8315fe..383e75b 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -115,6 +115,7 @@
 android.util.Xml
 
 android.util.proto.EncodedBuffer
+android.util.proto.ProtoFieldFilter
 android.util.proto.ProtoInputStream
 android.util.proto.ProtoOutputStream
 android.util.proto.ProtoParseException
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 1588e04..7a5b866 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -40,7 +40,9 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.Xml;
+import android.util.proto.ProtoFieldFilter;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -49,10 +51,13 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos.Tombstone;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -64,6 +69,7 @@
 import java.nio.file.attribute.PosixFilePermissions;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -392,6 +398,129 @@
         writeTimestamps(timestamps);
     }
 
+    /**
+     * Processes a tombstone file and adds it to the DropBox after filtering and applying
+     * rate limiting.
+     * Filtering removes memory sections from the tombstone proto to reduce size while preserving
+     * critical information. The filtered tombstone is then added to DropBox in both proto
+     * and text formats, with the text format derived from the filtered proto.
+     * Rate limiting is applied as it is the case with other crash types.
+     *
+     * @param ctx Context
+     * @param tombstone path to the tombstone
+     * @param processName the name of the process corresponding to the tombstone
+     * @param tmpFileLock the lock for reading/writing tmp files
+     */
+    public static void filterAndAddTombstoneToDropBox(
+                Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) {
+        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+        if (db == null) {
+            Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
+            return;
+        }
+        File filteredProto = null;
+        // Check if we should rate limit and abort early if needed.
+        DropboxRateLimiter.RateLimitResult rateLimitResult =
+                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+        if (rateLimitResult.shouldRateLimit()) return;
+
+        HashMap<String, Long> timestamps = readTimestamps();
+        try {
+            tmpFileLock.lock();
+            Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName());
+            // Create a temporary tombstone without memory sections.
+            filteredProto = createTempTombstoneWithoutMemory(tombstone);
+            Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName());
+
+            if (recordFileTimestamp(tombstone, timestamps)) {
+                // We need to attach the count indicating the number of dropped dropbox entries
+                // due to rate limiting. Do this by enclosing the proto tombsstone in a
+                // container proto that has the dropped entry count and the proto tombstone as
+                // bytes (to avoid the complexity of reading and writing nested protos).
+                Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox");
+                addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult);
+            }
+            // Always add the text version of the tombstone to the DropBox, in order to
+            // match the previous behaviour.
+            Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName()
+                    + " to dropbox");
+            addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult);
+
+        } catch (IOException | ProtoParseException e) {
+            Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName()
+                    + "' to DropBox. Error during processing or writing: " + e.getMessage(), e);
+        }  finally {
+            if (filteredProto != null) {
+                filteredProto.delete();
+            }
+            tmpFileLock.unlock();
+        }
+        writeTimestamps(timestamps);
+    }
+
+    /**
+     * Creates a temporary tombstone file by filtering out memory mapping fields.
+     * This ensures that the unneeded memory mapping data is removed from the tombstone
+     * before adding it to Dropbox
+     *
+     * @param tombstone the original tombstone file to process
+     * @return a temporary file containing the filtered tombstone data
+     * @throws IOException if an I/O error occurs during processing
+     */
+    private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException {
+        // Process the proto tombstone file and write it to a temporary file
+        File tombstoneProto =
+                File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR);
+        ProtoFieldFilter protoFilter =
+                new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS);
+
+        try (FileInputStream fis = new FileInputStream(tombstone);
+                BufferedInputStream bis = new BufferedInputStream(fis);
+                FileOutputStream fos = new FileOutputStream(tombstoneProto);
+                BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+            protoFilter.filter(bis, bos);
+            return tombstoneProto;
+        }
+    }
+
+    private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db,
+            HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) {
+        File tombstoneTextFile = null;
+
+        try {
+            tombstoneTextFile = File.createTempFile(tombstone.getName(),
+                ".pb.txt.tmp", TOMBSTONE_TMP_DIR);
+
+            // Create a ProcessBuilder to execute pbtombstone
+            ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath());
+            pb.redirectOutput(tombstoneTextFile);
+            Process process = pb.start();
+
+            // Wait 10 seconds for the process to complete
+            if (!process.waitFor(10, TimeUnit.SECONDS)) {
+                Slog.e(TAG, "pbtombstone timed out");
+                process.destroyForcibly();
+                return;
+            }
+
+            int exitCode = process.exitValue();
+            if (exitCode != 0) {
+                Slog.e(TAG, "pbtombstone failed with exit code " + exitCode);
+            } else {
+                final String headers = getBootHeadersToLogAndUpdate()
+                        + rateLimitResult.createHeader();
+                addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE,
+                        TAG_TOMBSTONE);
+            }
+        } catch (IOException | InterruptedException e) {
+            Slog.e(TAG, "Failed to process tombstone with pbtombstone", e);
+        } finally {
+            if (tombstoneTextFile != null) {
+                tombstoneTextFile.delete();
+            }
+        }
+    }
+
     private static void addAugmentedProtoToDropbox(
                 File tombstone, DropBoxManager db,
                 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f23d782..33c1229 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -137,16 +137,26 @@
             return;
         }
 
-        String processName = "UNKNOWN";
         final boolean isProtoFile = filename.endsWith(".pb");
+
+        // Only process the pb tombstone output, the text version will be generated in
+        // BootReceiver.filterAndAddTombstoneToDropBox through pbtombstone
+        if (Flags.protoTombstone() && !isProtoFile) {
+            return;
+        }
+
         File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
 
-        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
-        if (parsedTombstone.isPresent()) {
-            processName = parsedTombstone.get().getProcessName();
-        }
-        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+        final String processName = handleProtoTombstone(protoPath, isProtoFile)
+                .map(TombstoneFile::getProcessName)
+                .orElse("UNKNOWN");
 
+        if (Flags.protoTombstone()) {
+            BootReceiver.filterAndAddTombstoneToDropBox(mContext, path, processName, mTmpFileLock);
+        } else {
+            BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile,
+                    processName, mTmpFileLock);
+        }
         // TODO(b/339371242): An optimizer on WearOS is misbehaving and this member is being garbage
         // collected as it's never referenced inside this class outside of the constructor. But,
         // it's a file watcher, and needs to stay alive to do its job. So, add a cheap check here to
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index efdc9b8..5e35cf5 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -3,7 +3,7 @@
 
 flag {
     name: "proto_tombstone"
-    namespace: "proto_tombstone_ns"
+    namespace: "stability"
     description: "Use proto tombstones as source of truth for adding to dropbox"
     bug: "323857385"
 }