diff --git a/Android.bp b/Android.bp
index f3b2ebb4fc1742120a522386082e6b93c322bbb6..dde94a1282d752e6e5a176994c19e53d26a8c745 100644
--- a/Android.bp
+++ b/Android.bp
@@ -149,6 +149,7 @@ filegroup {
         ":framework-javastream-protos",
         ":statslog-framework-java-gen", // FrameworkStatsLog.java
         ":audio_policy_configuration_V7_0",
+        ":perfetto_trace_javastream_protos",
     ],
 }
 
diff --git a/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
new file mode 100644
index 0000000000000000000000000000000000000000..819cc8c7beef4009a4e4ed8e3a7d6cb8fe4cb731
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateIncrementalStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+    private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+    private final int mInstanceIndex;
+
+    CreateIncrementalStateArgs(DataSource dataSource, int instanceIndex) {
+        this.mDataSource = dataSource;
+        this.mInstanceIndex = instanceIndex;
+    }
+
+    /**
+     * Gets the datasource instance for this state with a lock.
+     * releaseDataSourceInstanceLocked must be called before this can be called again.
+     * @return The data source instance for this state.
+     *         Null if the datasource instance no longer exists.
+     */
+    public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+        return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+    }
+}
diff --git a/core/java/android/tracing/perfetto/CreateTlsStateArgs.java b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fad2d1b3e53715e524fe207c7045dad7d864b4d
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateTlsStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+    private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+    private final int mInstanceIndex;
+
+    CreateTlsStateArgs(DataSource dataSource, int instanceIndex) {
+        this.mDataSource = dataSource;
+        this.mInstanceIndex = instanceIndex;
+    }
+
+    /**
+     * Gets the datasource instance for this state with a lock.
+     * releaseDataSourceInstanceLocked must be called before this can be called again.
+     * @return The data source instance for this state.
+     *         Null if the datasource instance no longer exists.
+     */
+    public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+        return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+    }
+}
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e08aeef88e6859899de43744c74cfb536c23a7a
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -0,0 +1,163 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+/**
+ * Templated base class meant to be derived by embedders to create a custom data
+ * source.
+ *
+ * @param <DataSourceInstanceType> The type for the DataSource instances that will be created from
+ *                                 this DataSource type.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public abstract class DataSource<DataSourceInstanceType extends DataSourceInstance,
+        TlsStateType, IncrementalStateType> {
+    protected final long mNativeObj;
+
+    public final String name;
+
+    /**
+     * A function implemented by each datasource to create a new data source instance.
+     *
+     * @param configStream A ProtoInputStream to read the tracing instance's config.
+     * @return A new data source instance setup with the provided config.
+     */
+    public abstract DataSourceInstanceType createInstance(
+            ProtoInputStream configStream, int instanceIndex);
+
+    /**
+     * Constructor for datasource base class.
+     *
+     * @param name The fully qualified name of the datasource.
+     */
+    public DataSource(String name) {
+        this.name = name;
+        this.mNativeObj = nativeCreate(this, name);
+    }
+
+    /**
+     * The main tracing method. Tracing code should call this passing a lambda as
+     * argument, with the following signature: void(TraceContext).
+     * <p>
+     * The lambda will be called synchronously (i.e., always before trace()
+     * returns) only if tracing is enabled and the data source has been enabled in
+     * the tracing config.
+     * <p>
+     * The lambda can be called more than once per trace() call, in the case of
+     * concurrent tracing sessions (or even if the data source is instantiated
+     * twice within the same trace config).
+     *
+     * @param fun The tracing lambda that will be called with the tracing contexts of each active
+     *            tracing instance.
+     */
+    public final void trace(
+            TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
+        nativeTrace(mNativeObj, fun);
+    }
+
+    /**
+     * Flush any trace data from this datasource that has not yet been flushed.
+     */
+    public final void flush() {
+        nativeFlushAll(mNativeObj);
+    }
+
+    /**
+     * Override this method to create a custom TlsState object for your DataSource. A new instance
+     * will be created per trace instance per thread.
+     *
+     * NOTE: Should only be called from native side.
+     */
+    protected TlsStateType createTlsState(CreateTlsStateArgs<DataSourceInstanceType> args) {
+        return null;
+    }
+
+    /**
+     * Override this method to create and use a custom IncrementalState object for your DataSource.
+     *
+     * NOTE: Should only be called from native side.
+     */
+    protected IncrementalStateType createIncrementalState(
+            CreateIncrementalStateArgs<DataSourceInstanceType> args) {
+        return null;
+    }
+
+    /**
+     * Registers the data source on all tracing backends, including ones that
+     * connect after the registration. Doing so enables the data source to receive
+     * Setup/Start/Stop notifications and makes the trace() method work when
+     * tracing is enabled and the data source is selected.
+     * <p>
+     * NOTE: Once registered, we cannot unregister the data source. Therefore, we should avoid
+     * creating and registering data source where not strictly required. This is a fundamental
+     * limitation of Perfetto itself.
+     *
+     * @param params Params to initialize the datasource with.
+     */
+    public void register(DataSourceParams params) {
+        nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy);
+    }
+
+    /**
+     * Gets the datasource instance with a specified index.
+     * IMPORTANT: releaseDataSourceInstance must be called after using the datasource instance.
+     * @param instanceIndex The index of the datasource to lock and get.
+     * @return The DataSourceInstance at index instanceIndex.
+     *         Null if the datasource instance at the requested index doesn't exist.
+     */
+    public DataSourceInstanceType getDataSourceInstanceLocked(int instanceIndex) {
+        return (DataSourceInstanceType) nativeGetPerfettoInstanceLocked(mNativeObj, instanceIndex);
+    }
+
+    /**
+     * Unlock the datasource at the specified index.
+     * @param instanceIndex The index of the datasource to unlock.
+     */
+    protected void releaseDataSourceInstance(int instanceIndex) {
+        nativeReleasePerfettoInstanceLocked(mNativeObj, instanceIndex);
+    }
+
+    /**
+     * Called from native side when a new tracing instance starts.
+     *
+     * @param rawConfig byte array of the PerfettoConfig encoded proto.
+     * @return A new Java DataSourceInstance object.
+     */
+    private DataSourceInstanceType createInstance(byte[] rawConfig, int instanceIndex) {
+        final ProtoInputStream inputStream = new ProtoInputStream(rawConfig);
+        return this.createInstance(inputStream, instanceIndex);
+    }
+
+    private static native void nativeRegisterDataSource(
+            long dataSourcePtr, int bufferExhaustedPolicy);
+
+    private static native long nativeCreate(DataSource thiz, String name);
+    private static native void nativeTrace(
+            long nativeDataSourcePointer, TraceFunction traceFunction);
+    private static native void nativeFlushAll(long nativeDataSourcePointer);
+    private static native long nativeGetFinalizer();
+
+    private static native DataSourceInstance nativeGetPerfettoInstanceLocked(
+            long dataSourcePtr, int dsInstanceIdx);
+    private static native void nativeReleasePerfettoInstanceLocked(
+            long dataSourcePtr, int dsInstanceIdx);
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java
new file mode 100644
index 0000000000000000000000000000000000000000..49945013ae87d714ef1327041c8a4a687e769b62
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceInstance.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public abstract class DataSourceInstance implements AutoCloseable {
+    private final DataSource mDataSource;
+    private final int mInstanceIndex;
+
+    public DataSourceInstance(DataSource dataSource, int instanceIndex) {
+        this.mDataSource = dataSource;
+        this.mInstanceIndex = instanceIndex;
+    }
+
+    /**
+     * Executed when the tracing instance starts running.
+     * <p>
+     * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+     *       Anything that is run in this callback should execute quickly.
+     *
+     * @param args Start arguments.
+     */
+    protected void onStart(StartCallbackArguments args) {}
+
+    /**
+     * Executed when a flush is triggered.
+     * <p>
+     * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+     *       Anything that is run in this callback should execute quickly.
+     * @param args Flush arguments.
+     */
+    protected void onFlush(FlushCallbackArguments args) {}
+
+    /**
+     * Executed when the tracing instance is stopped.
+     * <p>
+     * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+     *       Anything that is run in this callback should execute quickly.
+     * @param args Stop arguments.
+     */
+    protected void onStop(StopCallbackArguments args) {}
+
+    @Override
+    public final void close() {
+        this.release();
+    }
+
+    /**
+     * Release the lock on the datasource once you are finished using it.
+     * Only required to be called when instance was retrieved with
+     * `DataSource#getDataSourceInstanceLocked`.
+     */
+    public final void release() {
+        mDataSource.releaseDataSourceInstance(mInstanceIndex);
+    }
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cd04e3d9a8b1e0c80bba18d65ab0df7ddbfdbe6
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceParams.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DataSource Parameters
+ *
+ * @hide
+ */
+public class DataSourceParams {
+    /**
+     * @hide
+     */
+    @IntDef(value = {
+        PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP,
+        PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PerfettoDsBufferExhausted {}
+
+    // If the data source runs out of space when trying to acquire a new chunk,
+    // it will drop data.
+    public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP = 0;
+
+    // If the data source runs out of space when trying to acquire a new chunk,
+    // it will stall, retry and eventually abort if a free chunk is not acquired
+    // after a while.
+    public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1;
+
+    public static DataSourceParams DEFAULTS =
+            new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP);
+
+    public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) {
+        this.bufferExhaustedPolicy = bufferExhaustedPolicy;
+    }
+
+    public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy;
+}
diff --git a/core/java/android/tracing/perfetto/FlushCallbackArguments.java b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecf6aee9ef50177eee40d04580e910cba6846aaa
--- /dev/null
+++ b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
@@ -0,0 +1,23 @@
+/*
+ * 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class FlushCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java
new file mode 100644
index 0000000000000000000000000000000000000000..da8c273fd14e04acd40b628d11de96b8bd80e596
--- /dev/null
+++ b/core/java/android/tracing/perfetto/InitArguments.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class InitArguments {
+    public final @PerfettoBackend int backends;
+
+    /**
+     * @hide
+     */
+    @IntDef(value = {
+            PERFETTO_BACKEND_IN_PROCESS,
+            PERFETTO_BACKEND_SYSTEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PerfettoBackend {}
+
+    // The in-process tracing backend. Keeps trace buffers in the process memory.
+    public static final int PERFETTO_BACKEND_IN_PROCESS = (1 << 0);
+
+    // The system tracing backend. Connects to the system tracing service (e.g.
+    // on Linux/Android/Mac uses a named UNIX socket).
+    public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1);
+
+    public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM);
+
+    public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS);
+
+    public InitArguments(@PerfettoBackend int backends) {
+        this.backends = backends;
+    }
+}
diff --git a/core/java/android/tracing/perfetto/Producer.java b/core/java/android/tracing/perfetto/Producer.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1b3eb75415702b404eeb749f261224a8257f289
--- /dev/null
+++ b/core/java/android/tracing/perfetto/Producer.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class Producer {
+
+    /**
+     * Initializes the global Perfetto producer.
+     *
+     * @param args arguments on how to initialize the Perfetto producer.
+     */
+    public static void init(InitArguments args) {
+        nativePerfettoProducerInit(args.backends);
+    }
+
+    private static native void nativePerfettoProducerInit(int backends);
+}
diff --git a/core/java/android/tracing/perfetto/StartCallbackArguments.java b/core/java/android/tracing/perfetto/StartCallbackArguments.java
new file mode 100644
index 0000000000000000000000000000000000000000..9739d271a13f305bf11695f0bacb4ec5ffa8bfe9
--- /dev/null
+++ b/core/java/android/tracing/perfetto/StartCallbackArguments.java
@@ -0,0 +1,23 @@
+/*
+ * 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class StartCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/StopCallbackArguments.java b/core/java/android/tracing/perfetto/StopCallbackArguments.java
new file mode 100644
index 0000000000000000000000000000000000000000..0cd1a188fa0c49e3b890bd2c61cfde0b476be2a9
--- /dev/null
+++ b/core/java/android/tracing/perfetto/StopCallbackArguments.java
@@ -0,0 +1,23 @@
+/*
+ * 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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class StopCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
new file mode 100644
index 0000000000000000000000000000000000000000..62941df70a4872e23ac5f4c393300b4befbb5344
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -0,0 +1,43 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import java.io.IOException;
+
+/**
+ * The interface for the trace function called from native on a trace call with a context.
+ *
+ * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
+        TlsStateType, IncrementalStateType> {
+
+    /**
+     * This function will be called synchronously (i.e., always before trace() returns) only if
+     * tracing is enabled and the data source has been enabled in the tracing config.
+     * It can be called more than once per trace() call, in the case of concurrent tracing sessions
+     * (or even if the data source is instantiated twice within the same trace config).
+     *
+     * @param ctx the tracing context to trace for in the trace function.
+     */
+    void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx)
+            throws IOException;
+}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bce26e007a195a345961e65eb232c341de6511b
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Argument passed to the lambda function passed to Trace().
+ *
+ * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
+        TlsStateType, IncrementalStateType> {
+
+    private final long mContextPtr;
+    private final TlsStateType mTlsState;
+    private final IncrementalStateType mIncrementalState;
+    private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
+
+    // Should only be created from the native side.
+    private TracingContext(long contextPtr, TlsStateType tlsState,
+            IncrementalStateType incrementalState) {
+        this.mContextPtr = contextPtr;
+        this.mTlsState = tlsState;
+        this.mIncrementalState = incrementalState;
+    }
+
+    /**
+     * Creates a new output stream to be used to write a trace packet to. The output stream will be
+     * encoded to the proto binary representation when the callback trace function finishes and
+     * send over to the native side to be included in the proto buffer.
+     *
+     * @return A proto output stream to write a trace packet proto to
+     */
+    public ProtoOutputStream newTracePacket() {
+        final ProtoOutputStream os = new ProtoOutputStream(0);
+        mTracePackets.add(os);
+        return os;
+    }
+
+    /**
+     * Forces a commit of the thread-local tracing data written so far to the
+     * service. This is almost never required (tracing data is periodically
+     * committed as trace pages are filled up) and has a non-negligible
+     * performance hit (requires an IPC + refresh of the current thread-local
+     * chunk). The only case when this should be used is when handling OnStop()
+     * asynchronously, to ensure sure that the data is committed before the
+     * Stop timeout expires.
+     */
+    public void flush() {
+        nativeFlush(this, mContextPtr);
+    }
+
+    /**
+     * Can optionally be used to store custom per-sequence
+     * session data, which is not reset when incremental state is cleared
+     * (e.g. configuration options).
+     *
+     * @return The TlsState instance for the tracing thread and instance.
+     */
+    public TlsStateType getCustomTlsState() {
+        return this.mTlsState;
+    }
+
+    /**
+     * Can optionally be used store custom per-sequence
+     * incremental data (e.g., interning tables).
+     *
+     * @return The current IncrementalState object instance.
+     */
+    public IncrementalStateType getIncrementalState() {
+        return this.mIncrementalState;
+    }
+
+    // Called from native to get trace packets
+    private byte[][] getAndClearAllPendingTracePackets() {
+        byte[][] res = new byte[mTracePackets.size()][];
+        for (int i = 0; i < mTracePackets.size(); i++) {
+            ProtoOutputStream tracePacket = mTracePackets.get(i);
+            res[i] = tracePacket.getBytes();
+        }
+
+        mTracePackets.clear();
+        return res;
+    }
+
+    // private static native void nativeFlush(long nativeDataSourcePointer);
+    private static native void nativeFlush(TracingContext thiz, long ctxPointer);
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2a744e343ccdb5bd312623a74b6d1ae3a2a93bd5..c8fd246a255bde3326fc6187c5e71898edb28c26 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -269,6 +269,9 @@ cc_library_shared_for_libandroid_runtime {
                 "android_window_WindowInfosListener.cpp",
                 "android_window_ScreenCapture.cpp",
                 "jni_common.cpp",
+                "android_tracing_PerfettoDataSource.cpp",
+                "android_tracing_PerfettoDataSourceInstance.cpp",
+                "android_tracing_PerfettoProducer.cpp",
             ],
 
             static_libs: [
@@ -282,6 +285,7 @@ cc_library_shared_for_libandroid_runtime {
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
+                "libperfetto_client_experimental",
             ],
 
             shared_libs: [
@@ -355,6 +359,7 @@ cc_library_shared_for_libandroid_runtime {
                 "server_configurable_flags",
                 "libimage_io",
                 "libultrahdr",
+                "libperfetto_c",
             ],
             export_shared_lib_headers: [
                 // our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 17aad43edb6bfaceb7ea1e1d4172b938cd2b2a30..7a16318f3276d0db3e9eed40774cdc9f8dcc62e8 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -220,6 +220,9 @@ extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
 extern int register_android_window_WindowInfosListener(JNIEnv* env);
 extern int register_android_window_ScreenCapture(JNIEnv* env);
 extern int register_jni_common(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
+extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
 
 // Namespace for Android Runtime flags applied during boot time.
 static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1675,6 +1678,10 @@ static const RegJNIRec gRegJNI[] = {
         REG_JNI(register_android_window_WindowInfosListener),
         REG_JNI(register_android_window_ScreenCapture),
         REG_JNI(register_jni_common),
+
+        REG_JNI(register_android_tracing_PerfettoDataSource),
+        REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
+        REG_JNI(register_android_tracing_PerfettoProducer),
 };
 
 /*
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d71069866d893d614d2be49183a5e9d4652f70ec
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -0,0 +1,437 @@
+/*
+ * Copyright 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 "Perfetto"
+
+#include "android_tracing_PerfettoDataSource.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jmethodID createInstance;
+    jmethodID createTlsState;
+    jmethodID createIncrementalState;
+} gPerfettoDataSourceClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+    jmethodID getAndClearAllPendingTracePackets;
+} gTracingContextClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gCreateTlsStateArgsClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gCreateIncrementalStateArgsClassInfo;
+
+static JavaVM* gVm;
+
+struct TlsState {
+    jobject jobj;
+};
+
+struct IncrementalState {
+    jobject jobj;
+};
+
+static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) {
+    jobjectArray packets =
+            (jobjectArray)env
+                    ->CallObjectMethod(jCtx,
+                                       gTracingContextClassInfo.getAndClearAllPendingTracePackets);
+    if (env->ExceptionOccurred()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+
+        LOG_ALWAYS_FATAL("Failed to call java context finalize method");
+    }
+
+    int packets_count = env->GetArrayLength(packets);
+    for (int i = 0; i < packets_count; i++) {
+        jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
+
+        jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+        int buffer_size = env->GetArrayLength(packet_proto_buffer);
+
+        struct PerfettoDsRootTracePacket trace_packet;
+        PerfettoDsTracerPacketBegin(ctx, &trace_packet);
+        PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
+                                 buffer_size);
+        PerfettoDsTracerPacketEnd(ctx, &trace_packet);
+    }
+}
+
+PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource,
+                                       std::string dataSourceName)
+      : dataSourceName(std::move(dataSourceName)),
+        mJavaDataSource(env->NewGlobalRef(javaDataSource)) {}
+
+jobject PerfettoDataSource::newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+                                        PerfettoDsInstanceIndex inst_id) {
+    jbyteArray configArray = env->NewByteArray(ds_config_size);
+
+    void* temp = env->GetPrimitiveArrayCritical((jarray)configArray, 0);
+    memcpy(temp, ds_config, ds_config_size);
+    env->ReleasePrimitiveArrayCritical(configArray, temp, 0);
+
+    jobject instance =
+            env->CallObjectMethod(mJavaDataSource, gPerfettoDataSourceClassInfo.createInstance,
+                                  configArray, inst_id);
+
+    if (env->ExceptionCheck()) {
+        LOGE_EX(env);
+        env->ExceptionClear();
+        LOG_ALWAYS_FATAL("Failed to create new Java Perfetto datasource instance");
+    }
+
+    return instance;
+}
+
+jobject PerfettoDataSource::createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gCreateTlsStateArgsClassInfo.clazz,
+                                                gCreateTlsStateArgsClassInfo.init, mJavaDataSource,
+                                                inst_id));
+
+    ScopedLocalRef<jobject> tslState(env,
+                                     env->CallObjectMethod(mJavaDataSource,
+                                                           gPerfettoDataSourceClassInfo
+                                                                   .createTlsState,
+                                                           args.get()));
+
+    if (env->ExceptionCheck()) {
+        LOGE_EX(env);
+        env->ExceptionClear();
+        LOG_ALWAYS_FATAL("Failed to create new Java Perfetto incremental state");
+    }
+
+    return env->NewGlobalRef(tslState.get());
+}
+
+jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env,
+                                                            PerfettoDsInstanceIndex inst_id) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz,
+                                                gCreateIncrementalStateArgsClassInfo.init,
+                                                mJavaDataSource, inst_id));
+
+    ScopedLocalRef<jobject> incrementalState(env,
+                                             env->CallObjectMethod(mJavaDataSource,
+                                                                   gPerfettoDataSourceClassInfo
+                                                                           .createIncrementalState,
+                                                                   args.get()));
+
+    if (env->ExceptionCheck()) {
+        LOGE_EX(env);
+        env->ExceptionClear();
+        LOG_ALWAYS_FATAL("Failed to create Java Perfetto incremental state");
+    }
+
+    return env->NewGlobalRef(incrementalState.get());
+}
+
+void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
+    PERFETTO_DS_TRACE(dataSource, ctx) {
+        TlsState* tls_state =
+                reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
+        IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+                PerfettoDsGetIncrementalState(&dataSource, &ctx));
+
+        ScopedLocalRef<jobject> jCtx(env,
+                                     env->NewObject(gTracingContextClassInfo.clazz,
+                                                    gTracingContextClassInfo.init, &ctx,
+                                                    tls_state->jobj, incr_state->jobj));
+
+        jclass objclass = env->GetObjectClass(traceFunction);
+        jmethodID method =
+                env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
+        if (method == 0) {
+            LOG_ALWAYS_FATAL("Failed to get method id");
+        }
+
+        env->ExceptionClear();
+
+        env->CallVoidMethod(traceFunction, method, jCtx.get());
+        if (env->ExceptionOccurred()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            LOG_ALWAYS_FATAL("Failed to call java trace method");
+        }
+
+        traceAllPendingPackets(env, jCtx.get(), &ctx);
+    }
+}
+
+void PerfettoDataSource::flushAll() {
+    PERFETTO_DS_TRACE(dataSource, ctx) {
+        PerfettoDsTracerFlush(&ctx, nullptr, nullptr);
+    }
+}
+
+PerfettoDataSource::~PerfettoDataSource() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteWeakGlobalRef(mJavaDataSource);
+}
+
+jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
+    const char* nativeString = env->GetStringUTFChars(name, 0);
+    PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+    env->ReleaseStringUTFChars(name, nativeString);
+
+    dataSource->incStrong((void*)nativeCreate);
+
+    return reinterpret_cast<jlong>(dataSource);
+}
+
+void nativeDestroy(void* ptr) {
+    PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
+    dataSource->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
+}
+
+void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+
+    datasource->trace(env, traceFunctionInterface);
+}
+
+void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+    auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
+    traceAllPendingPackets(env, jCtx, ctx);
+    PerfettoDsTracerFlush(ctx, nullptr, nullptr);
+}
+
+void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
+    datasource->flushAll();
+}
+
+void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
+                              int buffer_exhausted_policy) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
+
+    struct PerfettoDsParams params = PerfettoDsParamsDefault();
+    params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy;
+
+    params.user_arg = reinterpret_cast<void*>(datasource.get());
+
+    params.on_setup_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id,
+                            void* ds_config, size_t ds_config_size, void* user_arg,
+                            struct PerfettoDsOnSetupArgs*) -> void* {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+        ScopedLocalRef<jobject> java_data_source_instance(env,
+                                                          datasource->newInstance(env, ds_config,
+                                                                                  ds_config_size,
+                                                                                  inst_id));
+
+        auto* datasource_instance =
+                new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
+
+        return static_cast<void*>(datasource_instance);
+    };
+
+    params.on_create_tls_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+                                 struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+        jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
+
+        auto* tls_state = new TlsState(java_tls_state);
+
+        return static_cast<void*>(tls_state);
+    };
+
+    params.on_delete_tls_cb = [](void* ptr) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+        env->DeleteGlobalRef(tls_state->jobj);
+        delete tls_state;
+    };
+
+    params.on_create_incr_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+                                  struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+        jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
+
+        auto* incr_state = new IncrementalState(java_incr_state);
+        return static_cast<void*>(incr_state);
+    };
+
+    params.on_delete_incr_cb = [](void* ptr) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+        env->DeleteGlobalRef(incr_state->jobj);
+        delete incr_state;
+    };
+
+    params.on_start_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+                            struct PerfettoDsOnStartArgs*) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        datasource_instance->onStart(env);
+    };
+
+    params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+                            struct PerfettoDsOnFlushArgs*) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        datasource_instance->onFlush(env);
+    };
+
+    params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg,
+                           void* inst_ctx, struct PerfettoDsOnStopArgs*) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        datasource_instance->onStop(env);
+    };
+
+    params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
+                              void* inst_ctx) -> void {
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        delete datasource_instance;
+    };
+
+    PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params);
+}
+
+jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+                                        PerfettoDsInstanceIndex instance_idx) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
+            PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
+
+    if (datasource_instance == nullptr) {
+        // datasource instance doesn't exist
+        return nullptr;
+    }
+
+    return datasource_instance->GetJavaDataSourceInstance();
+}
+
+void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+                                         PerfettoDsInstanceIndex instance_idx) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
+}
+
+const JNINativeMethod gMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
+         (void*)nativeCreate},
+        {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace},
+        {"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
+        {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+        {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
+        {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;",
+         (void*)nativeGetPerfettoInstanceLocked},
+        {"nativeReleasePerfettoInstanceLocked", "(JI)V",
+         (void*)nativeReleasePerfettoInstanceLocked},
+};
+
+const JNINativeMethod gMethodsTracingContext[] = {
+        /* name, signature, funcPtr */
+        {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush},
+};
+
+int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/DataSource", gMethods,
+                                       NELEM(gMethods));
+
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    res = jniRegisterNativeMethods(env, "android/tracing/perfetto/TracingContext",
+                                   gMethodsTracingContext, NELEM(gMethodsTracingContext));
+
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    if (env->GetJavaVM(&gVm) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+    }
+
+    jclass clazz = env->FindClass("android/tracing/perfetto/DataSource");
+    gPerfettoDataSourceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gPerfettoDataSourceClassInfo.createInstance =
+            env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createInstance",
+                             "([BI)Landroid/tracing/perfetto/DataSourceInstance;");
+    gPerfettoDataSourceClassInfo.createTlsState =
+            env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createTlsState",
+                             "(Landroid/tracing/perfetto/CreateTlsStateArgs;)Ljava/lang/Object;");
+    gPerfettoDataSourceClassInfo.createIncrementalState =
+            env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createIncrementalState",
+                             "(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/"
+                             "Object;");
+
+    clazz = env->FindClass("android/tracing/perfetto/TracingContext");
+    gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>",
+                                                     "(JLjava/lang/Object;Ljava/lang/Object;)V");
+    gTracingContextClassInfo.getAndClearAllPendingTracePackets =
+            env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets",
+                             "()[[B");
+
+    clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs");
+    gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gCreateTlsStateArgsClassInfo.init =
+            env->GetMethodID(gCreateTlsStateArgsClassInfo.clazz, "<init>",
+                             "(Landroid/tracing/perfetto/DataSource;I)V");
+
+    clazz = env->FindClass("android/tracing/perfetto/CreateIncrementalStateArgs");
+    gCreateIncrementalStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gCreateIncrementalStateArgsClassInfo.init =
+            env->GetMethodID(gCreateIncrementalStateArgsClassInfo.clazz, "<init>",
+                             "(Landroid/tracing/perfetto/DataSource;I)V");
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
new file mode 100644
index 0000000000000000000000000000000000000000..4ddf1d8d4512663d50574aa34a8d39040db7653d
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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 "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSource : public virtual RefBase {
+public:
+    const std::string dataSourceName;
+    struct PerfettoDs dataSource = PERFETTO_DS_INIT();
+
+    PerfettoDataSource(JNIEnv* env, jobject java_data_source, std::string data_source_name);
+    ~PerfettoDataSource();
+
+    jobject newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+                        PerfettoDsInstanceIndex inst_id);
+
+    jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+    jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+    void trace(JNIEnv* env, jobject trace_function);
+    void flushAll();
+
+private:
+    jobject mJavaDataSource;
+    std::map<PerfettoDsInstanceIndex, PerfettoDataSourceInstance*> mInstances;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e659bf1c55e9560575dec8c6ecbb86f0c2e8610b
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 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 "Perfetto"
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gStartCallbackArgumentsClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gFlushCallbackArgumentsClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gStopCallbackArgumentsClassInfo;
+
+static JavaVM* gVm;
+
+void callJavaMethodWithArgsObject(JNIEnv* env, jobject classRef, jmethodID method, jobject args) {
+    ScopedLocalRef<jobject> localClassRef(env, env->NewLocalRef(classRef));
+
+    if (localClassRef == nullptr) {
+        ALOGE("Weak reference went out of scope");
+        return;
+    }
+
+    env->CallVoidMethod(localClassRef.get(), method, args);
+
+    if (env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+PerfettoDataSourceInstance::PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+                                                       PerfettoDsInstanceIndex inst_idx)
+      : inst_idx(inst_idx), mJavaDataSourceInstance(env->NewGlobalRef(javaDataSourceInstance)) {}
+
+PerfettoDataSourceInstance::~PerfettoDataSourceInstance() {
+    JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+    env->DeleteGlobalRef(mJavaDataSourceInstance);
+}
+
+void PerfettoDataSourceInstance::onStart(JNIEnv* env) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gStartCallbackArgumentsClassInfo.clazz,
+                                                gStartCallbackArgumentsClassInfo.init));
+    jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+    jmethodID mid = env->GetMethodID(cls, "onStart",
+                                     "(Landroid/tracing/perfetto/StartCallbackArguments;)V");
+
+    callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onFlush(JNIEnv* env) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gFlushCallbackArgumentsClassInfo.clazz,
+                                                gFlushCallbackArgumentsClassInfo.init));
+    jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+    jmethodID mid = env->GetMethodID(cls, "onFlush",
+                                     "(Landroid/tracing/perfetto/FlushCallbackArguments;)V");
+
+    callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onStop(JNIEnv* env) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gStopCallbackArgumentsClassInfo.clazz,
+                                                gStopCallbackArgumentsClassInfo.init));
+    jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+    jmethodID mid =
+            env->GetMethodID(cls, "onStop", "(Landroid/tracing/perfetto/StopCallbackArguments;)V");
+
+    callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env) {
+    if (env->GetJavaVM(&gVm) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+    }
+
+    jclass clazz = env->FindClass("android/tracing/perfetto/StartCallbackArguments");
+    gStartCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gStartCallbackArgumentsClassInfo.init =
+            env->GetMethodID(gStartCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+    clazz = env->FindClass("android/tracing/perfetto/FlushCallbackArguments");
+    gFlushCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gFlushCallbackArgumentsClassInfo.init =
+            env->GetMethodID(gFlushCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+    clazz = env->FindClass("android/tracing/perfetto/StopCallbackArguments");
+    gStopCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gStopCallbackArgumentsClassInfo.init =
+            env->GetMethodID(gStopCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
new file mode 100644
index 0000000000000000000000000000000000000000..d57765565d8a3b2259786c41e99cde1b98d2772f
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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 "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSourceInstance {
+public:
+    PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+                               PerfettoDsInstanceIndex inst_idx);
+    ~PerfettoDataSourceInstance();
+
+    void onStart(JNIEnv* env);
+    void onFlush(JNIEnv* env);
+    void onStop(JNIEnv* env);
+
+    jobject GetJavaDataSourceInstance() {
+        return mJavaDataSourceInstance;
+    }
+
+    PerfettoDsInstanceIndex getIndex() {
+        return inst_idx;
+    }
+
+private:
+    PerfettoDsInstanceIndex inst_idx;
+    jobject mJavaDataSourceInstance;
+};
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ce72f5893c19a0514fa3a111073b23f51aedb2a5
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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 "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSource.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) {
+    struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+    args.backends = (PerfettoBackendTypes)backends;
+    PerfettoProducerInit(args);
+}
+
+const JNINativeMethod gMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit},
+};
+
+int register_android_tracing_PerfettoProducer(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/Producer", gMethods,
+                                       NELEM(gMethods));
+
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c0581746e6f632dc09caf155508e622749aa79aa..531756ef23025db8e526681387e07379e0a2d3f9 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -83,6 +83,10 @@ android_test {
         "com.android.text.flags-aconfig-java",
         "flag-junit",
         "ravenwood-junit",
+        "perfetto_trace_java_protos",
+        "flickerlib-parsers",
+        "flickerlib-trace_processor_shell",
+        "mockito-target-extended-minus-junit4",
     ],
 
     libs: [
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b278dbafd5e1fb31210a8962cc4290bdc40b93c1
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -0,0 +1,664 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import static perfetto.protos.PerfettoTrace.TestEvent.PAYLOAD;
+import static perfetto.protos.PerfettoTrace.TestEvent.TestPayload.SINGLE_INT;
+import static perfetto.protos.PerfettoTrace.TracePacket.FOR_TESTING;
+
+import android.tools.common.ScenarioBuilder;
+import android.tools.common.Tag;
+import android.tools.common.io.TraceType;
+import android.tools.device.traces.TraceConfig;
+import android.tools.device.traces.TraceConfigs;
+import android.tools.device.traces.io.ResultReader;
+import android.tools.device.traces.io.ResultWriter;
+import android.tools.device.traces.monitors.PerfettoTraceMonitor;
+import android.tools.device.traces.monitors.TraceMonitor;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import perfetto.protos.PerfettoConfig;
+import perfetto.protos.PerfettoTrace;
+import perfetto.protos.TracePacketOuterClass;
+
+@RunWith(AndroidJUnit4.class)
+public class DataSourceTest {
+    private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+    private final ResultWriter mWriter = new ResultWriter()
+            .forScenario(new ScenarioBuilder()
+                    .forClass(createTempFile("temp", "").getName()).build())
+            .withOutputDir(mTracingDirectory)
+            .setRunComplete();
+
+    private final TraceConfigs mTraceConfig = new TraceConfigs(
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false)
+    );
+
+    private static TestDataSource sTestDataSource;
+
+    private static TestDataSource.DataSourceInstanceProvider sInstanceProvider;
+    private static TestDataSource.TlsStateProvider sTlsStateProvider;
+    private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider;
+
+    public DataSourceTest() throws IOException {}
+
+    @BeforeClass
+    public static void beforeAll() {
+        Producer.init(InitArguments.DEFAULTS);
+        setupProviders();
+        sTestDataSource = new TestDataSource(
+                (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream),
+                args -> sTlsStateProvider.provide(args),
+                args -> sIncrementalStateProvider.provide(args));
+        sTestDataSource.register(DataSourceParams.DEFAULTS);
+    }
+
+    private static void setupProviders() {
+        sInstanceProvider = (ds, idx, configStream) ->
+                new TestDataSource.TestDataSourceInstance(ds, idx);
+        sTlsStateProvider = args -> new TestDataSource.TestTlsState();
+        sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState();
+    }
+
+    @Before
+    public void setup() {
+        setupProviders();
+    }
+
+    @Test
+    public void canTraceData() throws InvalidProtocolBufferException {
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, 10);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
+    @Test
+    public void canUseTlsStateForCustomState() {
+        final int expectedStateTestValue = 10;
+        final AtomicInteger actualStateTestValue = new AtomicInteger();
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+
+            sTestDataSource.trace((ctx) -> {
+                TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                state.testStateValue = expectedStateTestValue;
+            });
+
+            sTestDataSource.trace((ctx) -> {
+                TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                actualStateTestValue.set(state.testStateValue);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue);
+    }
+
+    @Test
+    public void eachInstanceHasOwnTlsState() {
+        final int[] expectedStateTestValues = new int[] { 1, 2 };
+        final int[] actualStateTestValues = new int[] { 0, 0 };
+
+        final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+        final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor1.start();
+            try {
+                traceMonitor2.start();
+
+                AtomicInteger index = new AtomicInteger(0);
+                sTestDataSource.trace((ctx) -> {
+                    TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                    state.testStateValue = expectedStateTestValues[index.getAndIncrement()];
+                });
+
+                index.set(0);
+                sTestDataSource.trace((ctx) -> {
+                    TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                    actualStateTestValues[index.getAndIncrement()] = state.testStateValue;
+                });
+            } finally {
+                traceMonitor1.stop(mWriter);
+            }
+        } finally {
+            traceMonitor2.stop(mWriter);
+        }
+
+        Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]);
+        Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]);
+    }
+
+    @Test
+    public void eachThreadHasOwnTlsState() throws InterruptedException {
+        final int thread1ExpectedStateValue = 1;
+        final int thread2ExpectedStateValue = 2;
+
+        final AtomicInteger thread1ActualStateValue = new AtomicInteger();
+        final AtomicInteger thread2ActualStateValue = new AtomicInteger();
+
+        final CountDownLatch setUpLatch = new CountDownLatch(2);
+        final CountDownLatch setStateLatch = new CountDownLatch(2);
+        final CountDownLatch setOutStateLatch = new CountDownLatch(2);
+
+        final RunnableCreator createTask = (stateValue, stateOut) -> () -> {
+            Producer.init(InitArguments.DEFAULTS);
+
+            setUpLatch.countDown();
+
+            try {
+                setUpLatch.await(3, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            sTestDataSource.trace((ctx) -> {
+                TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                state.testStateValue = stateValue;
+                setStateLatch.countDown();
+            });
+
+            try {
+                setStateLatch.await(3, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            sTestDataSource.trace((ctx) -> {
+                stateOut.set(ctx.getCustomTlsState().testStateValue);
+                setOutStateLatch.countDown();
+            });
+        };
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+
+            new Thread(
+                    createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start();
+            new Thread(
+                    createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start();
+
+            setOutStateLatch.await(3, TimeUnit.SECONDS);
+
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue);
+        Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue);
+    }
+
+    @Test
+    public void incrementalStateIsReset() throws InterruptedException {
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build())
+                .setIncrementalTimeout(10)
+                .build();
+
+        final AtomicInteger testStateValue = new AtomicInteger();
+        try {
+            traceMonitor.start();
+
+            sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1);
+
+            // Timeout to make sure the incremental state is cleared.
+            Thread.sleep(1000);
+
+            sTestDataSource.trace(ctx ->
+                    testStateValue.set(ctx.getIncrementalState().testStateValue));
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        Truth.assertThat(testStateValue.get()).isNotEqualTo(1);
+    }
+
+    @Test
+    public void getInstanceConfigOnCreateInstance() throws IOException {
+        final int expectedDummyIntValue = 10;
+        AtomicReference<ProtoInputStream> configStream = new AtomicReference<>();
+        sInstanceProvider = (ds, idx, config) -> {
+            configStream.set(config);
+            return new TestDataSource.TestDataSourceInstance(ds, idx);
+        };
+
+        final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name)
+                        .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields(
+                                PerfettoConfig.TestConfig.DummyFields.newBuilder()
+                                        .setFieldInt32(expectedDummyIntValue)
+                                        .build())
+                                .build())
+                        .build())
+                .build();
+
+        try {
+            monitor.start();
+        } finally {
+            monitor.stop(mWriter);
+        }
+
+        int configDummyIntValue = 0;
+        while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            if (configStream.get().getFieldNumber()
+                    == (int) PerfettoTrace.DataSourceConfig.FOR_TESTING) {
+                final long forTestingToken = configStream.get()
+                        .start(PerfettoTrace.DataSourceConfig.FOR_TESTING);
+                while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    if (configStream.get().getFieldNumber()
+                            == (int) PerfettoTrace.TestConfig.DUMMY_FIELDS) {
+                        final long dummyFieldsToken = configStream.get()
+                                .start(PerfettoTrace.TestConfig.DUMMY_FIELDS);
+                        while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                            if (configStream.get().getFieldNumber()
+                                    == (int) PerfettoTrace.TestConfig.DummyFields.FIELD_INT32) {
+                                int val = configStream.get().readInt(
+                                        PerfettoTrace.TestConfig.DummyFields.FIELD_INT32);
+                                if (val != 0) {
+                                    configDummyIntValue = val;
+                                    break;
+                                }
+                            }
+                        }
+                        configStream.get().end(dummyFieldsToken);
+                        break;
+                    }
+                }
+                configStream.get().end(forTestingToken);
+                break;
+            }
+        }
+
+        Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue);
+    }
+
+    @Test
+    public void multipleTraceInstances() throws IOException, InterruptedException {
+        final int instanceCount = 3;
+
+        final List<TraceMonitor> monitors = new ArrayList<>();
+        final List<ResultWriter> writers = new ArrayList<>();
+
+        for (int i = 0; i < instanceCount; i++) {
+            final ResultWriter writer = new ResultWriter()
+                    .forScenario(new ScenarioBuilder()
+                            .forClass(createTempFile("temp", "").getName()).build())
+                    .withOutputDir(mTracingDirectory)
+                    .setRunComplete();
+            writers.add(writer);
+        }
+
+        // Start at 1 because 0 is considered null value so payload will be ignored in that case
+        TestDataSource.TestTlsState.lastIndex = 1;
+
+        final AtomicInteger traceCallCount = new AtomicInteger();
+        final CountDownLatch latch = new CountDownLatch(instanceCount);
+
+        try {
+            // Start instances
+            for (int i = 0; i < instanceCount; i++) {
+                final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                        .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                                .setName(sTestDataSource.name).build()).build();
+                monitors.add(traceMonitor);
+                traceMonitor.start();
+            }
+
+            // Trace the stateIndex of the tracing instance.
+            sTestDataSource.trace(ctx -> {
+                final int testIntValue = ctx.getCustomTlsState().stateIndex;
+                traceCallCount.incrementAndGet();
+
+                final ProtoOutputStream os = ctx.newTracePacket();
+                long forTestingToken = os.start(FOR_TESTING);
+                long payloadToken = os.start(PAYLOAD);
+                os.write(SINGLE_INT, testIntValue);
+                os.end(payloadToken);
+                os.end(forTestingToken);
+
+                latch.countDown();
+            });
+        } finally {
+            // Stop instances
+            for (int i = 0; i < instanceCount; i++) {
+                final TraceMonitor monitor = monitors.get(i);
+                final ResultWriter writer = writers.get(i);
+                monitor.stop(writer);
+            }
+        }
+
+        latch.await(3, TimeUnit.SECONDS);
+        Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount);
+
+        for (int i = 0; i < instanceCount; i++) {
+            final int expectedTracedValue = i + 1;
+            final ResultWriter writer = writers.get(i);
+            final ResultReader reader = new ResultReader(writer.write(), mTraceConfig);
+            final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+            assert rawProtoFromFile != null;
+            final perfetto.protos.TraceOuterClass.Trace trace =
+                    perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile);
+
+            Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+            final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                    .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+            Truth.assertWithMessage("One packet has for testing data")
+                    .that(tracePackets).hasSize(1);
+
+            final List<TracePacketOuterClass.TracePacket> matchingPackets =
+                    tracePackets.stream()
+                            .filter(it -> it.getForTesting().getPayload()
+                                    .getSingleInt() == expectedTracedValue).toList();
+            Truth.assertWithMessage(
+                            "One packet has testing data with a payload with the expected value")
+                    .that(matchingPackets).hasSize(1);
+        }
+    }
+
+    @Test
+    public void onStartCallbackTriggered() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+        sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {
+                            callbackCalled.set(true);
+                            latch.countDown();
+                        },
+                        (args) -> {},
+                        (args) -> {}
+        );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        Truth.assertThat(callbackCalled.get()).isFalse();
+        try {
+            traceMonitor.start();
+            latch.await(3, TimeUnit.SECONDS);
+            Truth.assertThat(callbackCalled.get()).isTrue();
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+    }
+
+    @Test
+    public void onFlushCallbackTriggered() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+        sInstanceProvider = (ds, idx, config) ->
+                new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {},
+                        (args) -> {
+                            callbackCalled.set(true);
+                            latch.countDown();
+                        },
+                        (args) -> {}
+                );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            Truth.assertThat(callbackCalled.get()).isFalse();
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, 10);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        latch.await(3, TimeUnit.SECONDS);
+        Truth.assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    public void onStopCallbackTriggered() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+        sInstanceProvider = (ds, idx, config) ->
+                new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {},
+                        (args) -> {},
+                        (args) -> {
+                            callbackCalled.set(true);
+                            latch.countDown();
+                        }
+                );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            Truth.assertThat(callbackCalled.get()).isFalse();
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        latch.await(3, TimeUnit.SECONDS);
+        Truth.assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException {
+        final Object testObject = new Object();
+
+        sInstanceProvider = (ds, idx, configStream) -> {
+            final TestDataSource.TestDataSourceInstance dsInstance =
+                    new TestDataSource.TestDataSourceInstance(ds, idx);
+            dsInstance.testObject = testObject;
+            return dsInstance;
+        };
+
+        sTlsStateProvider = args -> {
+            final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState();
+
+            try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+                         args.getDataSourceInstanceLocked()) {
+                if (dataSourceInstance != null) {
+                    tlsState.testStateValue = dataSourceInstance.testObject.hashCode();
+                }
+            }
+
+            return tlsState;
+        };
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt()
+                        == testObject.hashCode()).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
+    @Test
+    public void canUseDataSourceInstanceToCreateIncrementalState()
+            throws InvalidProtocolBufferException {
+        final Object testObject = new Object();
+
+        sInstanceProvider = (ds, idx, configStream) -> {
+            final TestDataSource.TestDataSourceInstance dsInstance =
+                    new TestDataSource.TestDataSourceInstance(ds, idx);
+            dsInstance.testObject = testObject;
+            return dsInstance;
+        };
+
+        sIncrementalStateProvider = args -> {
+            final TestDataSource.TestIncrementalState incrementalState =
+                    new TestDataSource.TestIncrementalState();
+
+            try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+                    args.getDataSourceInstanceLocked()) {
+                if (dataSourceInstance != null) {
+                    incrementalState.testStateValue = dataSourceInstance.testObject.hashCode();
+                }
+            }
+
+            return incrementalState;
+        };
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt()
+                        == testObject.hashCode()).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
+    interface RunnableCreator {
+        Runnable create(int state, AtomicInteger stateOut);
+    }
+}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..d78f78b1cb0e8c5c96b076851da2269ec89b7249
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
@@ -0,0 +1,122 @@
+/*
+ * 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance,
+        TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> {
+    private final DataSourceInstanceProvider mDataSourceInstanceProvider;
+    private final TlsStateProvider mTlsStateProvider;
+    private final IncrementalStateProvider mIncrementalStateProvider;
+
+    interface DataSourceInstanceProvider {
+        TestDataSourceInstance provide(
+                TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream);
+    }
+
+    interface TlsStateProvider {
+        TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args);
+    }
+
+    interface IncrementalStateProvider {
+        TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args);
+    }
+
+    public TestDataSource() {
+        this((ds, idx, config) -> new TestDataSourceInstance(ds, idx),
+                args -> new TestTlsState(), args -> new TestIncrementalState());
+    }
+
+    public TestDataSource(
+            DataSourceInstanceProvider dataSourceInstanceProvider,
+            TlsStateProvider tlsStateProvider,
+            IncrementalStateProvider incrementalStateProvider
+    ) {
+        super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString());
+        this.mDataSourceInstanceProvider = dataSourceInstanceProvider;
+        this.mTlsStateProvider = tlsStateProvider;
+        this.mIncrementalStateProvider = incrementalStateProvider;
+    }
+
+    @Override
+    public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+        return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream);
+    }
+
+    @Override
+    public TestTlsState createTlsState(CreateTlsStateArgs args) {
+        return mTlsStateProvider.provide(args);
+    }
+
+    @Override
+    public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) {
+        return mIncrementalStateProvider.provide(args);
+    }
+
+    public static class TestTlsState {
+        public int testStateValue;
+        public int stateIndex = lastIndex++;
+
+        public static int lastIndex = 0;
+    }
+
+    public static class TestIncrementalState {
+        public int testStateValue;
+    }
+
+    public static class TestDataSourceInstance extends DataSourceInstance {
+        public Object testObject;
+        Consumer<StartCallbackArguments> mStartCallback;
+        Consumer<FlushCallbackArguments> mFlushCallback;
+        Consumer<StopCallbackArguments> mStopCallback;
+
+        public TestDataSourceInstance(DataSource dataSource, int instanceIndex) {
+            this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {});
+        }
+
+        public TestDataSourceInstance(
+                DataSource dataSource,
+                int instanceIndex,
+                Consumer<StartCallbackArguments> startCallback,
+                Consumer<FlushCallbackArguments> flushCallback,
+                Consumer<StopCallbackArguments> stopCallback) {
+            super(dataSource, instanceIndex);
+            this.mStartCallback = startCallback;
+            this.mFlushCallback = flushCallback;
+            this.mStopCallback = stopCallback;
+        }
+
+        @Override
+        public void onStart(StartCallbackArguments args) {
+            this.mStartCallback.accept(args);
+        }
+
+        @Override
+        public void onFlush(FlushCallbackArguments args) {
+            this.mFlushCallback.accept(args);
+        }
+
+        @Override
+        public void onStop(StopCallbackArguments args) {
+            this.mStopCallback.accept(args);
+        }
+    }
+}