Merge "[GH] Use projectOrArtifact for appcompat-lint's core dependency" into androidx-main
diff --git a/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index d128244..8176114 100644
--- a/appcompat/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
@@ -242,6 +242,10 @@
             android:configChanges="orientation|screenSize|keyboardHidden"/>
 
         <activity
+            android:name="androidx.appcompat.app.g3.FilternatorActivityWithCustomDefault"
+            android:configChanges="orientation|screenSize|keyboardHidden"/>
+
+        <activity
             android:name="androidx.appcompat.app.g3.OldTranslateActivity"
             android:configChanges="screenSize|keyboardHidden|orientation|smallestScreenSize|screenLayout"
             android:recreateOnConfigChanges="mcc|mnc"
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorActivityWithCustomDefault.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorActivityWithCustomDefault.java
new file mode 100644
index 0000000..f8763e9
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorActivityWithCustomDefault.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appcompat.app.g3;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+
+import java.util.concurrent.CountDownLatch;
+
+public class FilternatorActivityWithCustomDefault extends AppCompatActivity {
+    public static CountDownLatch configurationLatch = new CountDownLatch(1);
+    public static Exception configurationException = null;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Configuration initialConfig =
+                new Configuration(getApplication().getResources().getConfiguration());
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+
+        super.onCreate(savedInstanceState);
+
+        if (!initialConfig.equals(getApplication().getResources().getConfiguration())) {
+            throw new IllegalStateException("Base configuration got messed up");
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        Configuration actualConfig = getResources().getConfiguration();
+        if (actualConfig.equals(newConfig)) {
+            int diff = actualConfig.diff(newConfig);
+            configurationException = new RuntimeException("Configuration changes not correctly "
+                    + "reflected in getResources().getConfiguration(), diff is " + diff + ", "
+                    + "actual config is " + actualConfig + ", new config is " + newConfig);
+        }
+
+        if (configurationLatch.getCount() > 0) {
+            configurationLatch.countDown();
+        }
+    }
+}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
new file mode 100644
index 0000000..45357c5
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appcompat.app.g3
+
+import androidx.appcompat.Orientation
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.appcompat.withOrientation
+import androidx.lifecycle.Lifecycle
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Regression test for b/236394773, adapted from GmsCore's own tests. The activity used for this
+ * test has been modified to always call AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES).
+ *
+ * The primary purpose of this test is to ensure that the application configuration is not
+ * accidentally modified when we modify the activity configuration.
+ */
+@Suppress("SameParameterValue")
+@LargeTest
+@SdkSuppress(minSdkVersion = 18) // UiDevice
+@RunWith(AndroidJUnit4::class)
+class FilternatorTestWithCustomDefault {
+    @get:Rule
+    val activityRule = ActivityScenarioRule(FilternatorActivityWithCustomDefault::class.java)
+
+    private lateinit var uiDevice: UiDevice
+
+    @Before
+    fun setup() {
+        uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+        }
+    }
+
+    @After
+    fun teardown() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+        }
+    }
+
+    @Test
+    fun testConfigurationUpdatedOnLandscapeMode() {
+        // Wait for the activity to fully start before rotating,
+        // otherwise we won't receive onConfigurationChanged.
+        val activity = activityRule.withActivity { this }
+        waitUntilState(activity, Lifecycle.State.RESUMED)
+
+        // Rotate and wait for the activity to check that
+        // configuration has been properly updated.
+        uiDevice.withOrientation(Orientation.LEFT) {
+            FilternatorActivity.configurationLatch.await(5000, TimeUnit.MILLISECONDS)
+            assertThat(FilternatorActivity.configurationException).isNull()
+        }
+    }
+}
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java
index 981c192..5ce91f2 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java
@@ -225,14 +225,16 @@
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
-        if (mResources != null) {
-            // The real (and thus managed) resources object was already updated
-            // by ResourcesManager, so pull the current metrics from there.
-            final DisplayMetrics newMetrics = super.getResources().getDisplayMetrics();
-            mResources.updateConfiguration(newConfig, newMetrics);
-        }
-
+        // The delegate may modify the real resources object or the config param to implement its
+        // desired configuration overrides. Let it do it's thing and then use the resulting state.
         getDelegate().onConfigurationChanged(newConfig);
+
+        // Manually propagate configuration changes to our unmanaged resources object.
+        if (mResources != null) {
+            final Configuration currConfig = super.getResources().getConfiguration();
+            final DisplayMetrics currMetrics = super.getResources().getDisplayMetrics();
+            mResources.updateConfiguration(currConfig, currMetrics);
+        }
     }
 
     @Override
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index 8219742..09c00d772 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -366,7 +366,7 @@
         if (sCanApplyOverrideConfiguration
                 && baseContext instanceof android.view.ContextThemeWrapper) {
             final Configuration config = createOverrideConfigurationForDayNight(
-                    baseContext, modeToApply, null);
+                    baseContext, modeToApply, null, false);
             if (DEBUG) {
                 Log.d(TAG, String.format("Attempting to apply config to base context: %s",
                         config.toString()));
@@ -386,7 +386,7 @@
         // Again, but using the AppCompat version of ContextThemeWrapper.
         if (baseContext instanceof ContextThemeWrapper) {
             final Configuration config = createOverrideConfigurationForDayNight(
-                    baseContext, modeToApply, null);
+                    baseContext, modeToApply, null, false);
             if (DEBUG) {
                 Log.d(TAG, String.format("Attempting to apply config to base context: %s",
                         config.toString()));
@@ -443,7 +443,7 @@
         }
 
         final Configuration config = createOverrideConfigurationForDayNight(
-                baseContext, modeToApply, configOverlay);
+                baseContext, modeToApply, configOverlay, true);
         if (DEBUG) {
             Log.d(TAG, String.format("Applying night mode using ContextThemeWrapper and "
                     + "applyOverrideConfiguration(). Config: %s", config.toString()));
@@ -664,6 +664,10 @@
         // Re-apply Day/Night with the new configuration but disable recreations. Since this
         // configuration change has only just happened we can safely just update the resources now
         applyDayNight(false);
+
+        // We may have just changed the resource configuration. Make sure that everyone after us
+        // sees the same configuration by modifying the parameter's internal state.
+        newConfig.updateFrom(mContext.getResources().getConfiguration());
     }
 
     @Override
@@ -2464,7 +2468,7 @@
     @NonNull
     private Configuration createOverrideConfigurationForDayNight(
             @NonNull Context context, @ApplyableNightMode final int mode,
-            @Nullable Configuration configOverlay) {
+            @Nullable Configuration configOverlay, boolean ignoreFollowSystem) {
         int newNightMode;
         switch (mode) {
             case MODE_NIGHT_YES:
@@ -2475,11 +2479,17 @@
                 break;
             default:
             case MODE_NIGHT_FOLLOW_SYSTEM:
-                // If we're following the system, we just use the system default from the
-                // application context
-                final Configuration appConfig =
-                        context.getApplicationContext().getResources().getConfiguration();
-                newNightMode = appConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+                if (ignoreFollowSystem) {
+                    // We're generating an overlay to be used on top of the system configuration,
+                    // so use whatever's already there.
+                    newNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
+                } else {
+                    // If we're following the system, we just use the system default from the
+                    // application context
+                    final Configuration appConfig =
+                            context.getApplicationContext().getResources().getConfiguration();
+                    newNightMode = appConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+                }
                 break;
         }
 
@@ -2508,7 +2518,7 @@
         boolean handled = false;
 
         final Configuration overrideConfig =
-                createOverrideConfigurationForDayNight(mContext, mode, null);
+                createOverrideConfigurationForDayNight(mContext, mode, null, false);
 
         final boolean activityHandlingUiMode = isActivityManifestHandlingUiMode(mContext);
         final Configuration currentConfiguration = mEffectiveConfiguration == null
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java
index 56d9ec7..b8c582d 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/view/ContextThemeWrapper.java
@@ -33,6 +33,11 @@
  * A context wrapper that allows you to modify or replace the theme of the wrapped context.
  */
 public class ContextThemeWrapper extends ContextWrapper {
+    /**
+     * Lazily-populated configuration object representing an empty, default configuration.
+     */
+    private static Configuration sEmptyConfig;
+
     private int mThemeResource;
     private Resources.Theme mTheme;
     private LayoutInflater mInflater;
@@ -113,7 +118,10 @@
 
     private Resources getResourcesInternal() {
         if (mResources == null) {
-            if (mOverrideConfiguration == null) {
+            if (isEmptyConfiguration(mOverrideConfiguration)) {
+                // If we're not applying any overrides, use the base context's resources. On API
+                // 26+, this will avoid pulling in resources that share a backing implementation
+                // with the application context.
                 mResources = super.getResources();
             } else if (Build.VERSION.SDK_INT >= 17) {
                 final Context resContext =
@@ -203,6 +211,26 @@
         return getResources().getAssets();
     }
 
+    /**
+     * @return {@code true} if the specified configuration is {@code null} or is a no-op when
+     *         used as a configuration overlay
+     */
+    private static boolean isEmptyConfiguration(Configuration overrideConfiguration) {
+        if (overrideConfiguration == null) {
+            return true;
+        }
+
+        if (sEmptyConfig == null) {
+            Configuration emptyConfig = new Configuration();
+            // Workaround for incorrect default fontScale on earlier SDKs (b/29924927). Note
+            // that Configuration.setToDefaults() is *not* a no-op configuration overlay.
+            emptyConfig.fontScale = 0.0f;
+            sEmptyConfig = emptyConfig;
+        }
+
+        return overrideConfiguration.equals(sEmptyConfig);
+    }
+
     @RequiresApi(17)
     static class Api17Impl {
         private Api17Impl() {
diff --git a/benchmark/benchmark-macro-junit4/api/public_plus_experimental_current.txt b/benchmark/benchmark-macro-junit4/api/public_plus_experimental_current.txt
index 8b7dffc..0e381a6 100644
--- a/benchmark/benchmark-macro-junit4/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-macro-junit4/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
   @RequiresApi(28) @androidx.benchmark.macro.ExperimentalBaselineProfilesApi public final class BaselineProfileRule implements org.junit.rules.TestRule {
     ctor public BaselineProfileRule();
     method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+    method public void collectBaselineProfile(String packageName, optional java.util.List<java.lang.String> packageFilters, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
     method public void collectBaselineProfile(String packageName, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
   }
 
diff --git a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt
index 63d2072..a65f294 100644
--- a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt
+++ b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt
@@ -80,15 +80,20 @@
     /**
      * Collects baseline profiles for a critical user journey.
      * @param packageName Package name of the app for which profiles are to be generated.
+     * @param packageFilters List of package names to use as a filter for the generated profiles.
+     *  By default no filters are applied. Note that this works only when the code is not obfuscated.
      * @param [profileBlock] defines the critical user journey.
      */
+    @JvmOverloads
     public fun collectBaselineProfile(
         packageName: String,
+        packageFilters: List<String> = emptyList(),
         profileBlock: MacrobenchmarkScope.() -> Unit
     ) {
         collectBaselineProfile(
-            currentDescription.toUniqueName(),
+            uniqueName = currentDescription.toUniqueName(),
             packageName = packageName,
+            packageFilters = packageFilters,
             profileBlock = profileBlock
         )
     }
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index ed523f7..53fcd5a 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -13,6 +13,7 @@
   }
 
   public final class BaselineProfilesKt {
+    method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void collectBaselineProfile(String uniqueName, String packageName, optional java.util.List<java.lang.String> packageFilters, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
     method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void collectBaselineProfile(String uniqueName, String packageName, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
   }
 
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index f5ad12a..e11d2c8 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -33,9 +33,11 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 @RequiresApi(28)
+@JvmOverloads
 fun collectBaselineProfile(
     uniqueName: String,
     packageName: String,
+    packageFilters: List<String> = emptyList(),
     profileBlock: MacrobenchmarkScope.() -> Unit
 ) {
     require(Build.VERSION.SDK_INT >= 28) {
@@ -79,17 +81,32 @@
         Log.d(TAG, "Converting to human readable profile format")
         // Look at reference profile first, and then fallback to current profile
         val profile = profile(apkPath, listOf(referenceProfile, currentProfile))
+
+        // Filters the profile output with the given set or rules.
+        // Note that the filter rules are package name but the profile file lines contain
+        // jvm method signature, ex: `HSPLandroidx/room/RoomDatabase;-><init>()V`.
+        // In order to simplify this for developers we transform the filters from regular package names.
+        val filteredProfile = if (packageFilters.isEmpty()) profile else {
+            // Ensure that the package name ends with `/`
+            val fixedPackageFilters = packageFilters
+                .map { "${it.replace(".", "/")}${if (it.endsWith(".")) "" else "/"}" }
+            profile
+                .lines()
+                .filter { line -> fixedPackageFilters.any { line.contains(it) } }
+                .joinToString(System.lineSeparator())
+        }
+
         InstrumentationResults.instrumentationReport {
             val fileName = "$uniqueName-baseline-prof.txt"
             val absolutePath = Outputs.writeFile(fileName, "baseline-profile") {
-                it.writeText(profile)
+                it.writeText(filteredProfile)
             }
             // Write a file with a timestamp to be able to disambiguate between runs with the same
             // unique name.
             val tsFileName = "$uniqueName-baseline-prof-${Outputs.dateToFileName()}.txt"
             val tsAbsolutePath = Outputs.writeFile(tsFileName, "baseline-profile-ts") {
                 Log.d(TAG, "Pull Baseline Profile with: `adb pull \"${it.absolutePath}\" .`")
-                it.writeText(profile)
+                it.writeText(filteredProfile)
             }
             val totalRunTime = System.nanoTime() - startTime
             val summary = summaryRecord(totalRunTime, absolutePath, tsAbsolutePath)
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index b42a78b..25e882a 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -53,6 +53,15 @@
         </activity>
 
         <activity
+            android:name=".EmptyActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="androidx.benchmark.integration.macrobenchmark.target.EMPTY_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
             android:name=".RecyclerViewActivity"
             android:exported="true">
             <intent-filter>
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/EmptyActivity.kt b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/EmptyActivity.kt
new file mode 100644
index 0000000..3c30af3
--- /dev/null
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/EmptyActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 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 androidx.benchmark.integration.macrobenchmark.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class EmptyActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        findViewById<TextView>(R.id.txtNotice).setText(R.string.app_notice)
+    }
+}
diff --git a/benchmark/integration-tests/macrobenchmark/build.gradle b/benchmark/integration-tests/macrobenchmark/build.gradle
index 0aee23f..7bfa4bb 100644
--- a/benchmark/integration-tests/macrobenchmark/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark/build.gradle
@@ -38,6 +38,7 @@
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testUiautomator)
+    androidTestImplementation(libs.testExtTruth)
 }
 
 // Allow usage of Kotlin's @OptIn.
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt
new file mode 100644
index 0000000..c47cb7c
--- /dev/null
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.DeviceInfo
+import com.google.common.truth.Truth.assertThat
+import androidx.benchmark.Outputs
+import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
+import androidx.benchmark.macro.junit4.BaselineProfileRule
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import java.io.File
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+@SdkSuppress(minSdkVersion = 29)
+@OptIn(ExperimentalBaselineProfilesApi::class)
+class BaselineProfileFilterTest {
+
+    @get:Rule
+    val baselineRule = BaselineProfileRule()
+
+    @Test
+    fun baselineProfilesFilter() {
+        Assume.assumeTrue(DeviceInfo.isRooted)
+
+        // Collects the baseline profile
+        baselineRule.collectBaselineProfile(
+            packageName = PACKAGE_NAME,
+            packageFilters = listOf(PACKAGE_NAME),
+            profileBlock = {
+                startActivityAndWait(Intent(ACTION))
+                device.waitForIdle()
+            }
+        )
+
+        // Asserts the output of the baseline profile
+        val lines = File(Outputs.outputDirectory, BASELINE_PROFILE_OUTPUT_FILE_NAME).readLines()
+        assertThat(lines).containsExactly(
+            "HSPLandroidx/benchmark/integration/macrobenchmark/target/EmptyActivity;" +
+                "-><init>()V",
+            "HSPLandroidx/benchmark/integration/macrobenchmark/target/EmptyActivity;" +
+                "->onCreate(Landroid/os/Bundle;)V",
+            "Landroidx/benchmark/integration/macrobenchmark/target/EmptyActivity;",
+        )
+    }
+
+    companion object {
+        private const val PACKAGE_NAME =
+            "androidx.benchmark.integration.macrobenchmark.target"
+        private const val ACTION =
+            "androidx.benchmark.integration.macrobenchmark.target.EMPTY_ACTIVITY"
+
+        // Note: this name is automatically generated starting from class and method name,
+        // according to the patter `<class>_<method>-baseline-prof.txt`. Changes for class and
+        // method names should be reflected here in order for the test to succeed.
+        private const val BASELINE_PROFILE_OUTPUT_FILE_NAME =
+            "BaselineProfileFilterTest_baselineProfilesFilter-baseline-prof.txt"
+    }
+}
diff --git a/busytown/androidx.sh b/busytown/androidx.sh
index 50396d2..7813bc8 100755
--- a/busytown/androidx.sh
+++ b/busytown/androidx.sh
@@ -21,6 +21,7 @@
   if ! impl/build.sh buildOnServer checkExternalLicenses listTaskOutputs validateProperties \
       -Pandroidx.enableComposeCompilerMetrics=true \
       -Pandroidx.enableComposeCompilerReports=true \
+      --no-daemon \
       --profile "$@"; then
     EXIT_VALUE=1
   fi
diff --git a/busytown/impl/build-metalava-and-androidx.sh b/busytown/impl/build-metalava-and-androidx.sh
index 5023d43..d24f86c 100755
--- a/busytown/impl/build-metalava-and-androidx.sh
+++ b/busytown/impl/build-metalava-and-androidx.sh
@@ -5,9 +5,9 @@
 
 androidxArguments="$*"
 
-WORKING_DIR="$(pwd)"
 SCRIPTS_DIR="$(cd $(dirname $0)/.. && pwd)"
 cd "$SCRIPTS_DIR/../../.."
+CHECKOUT_ROOT="$(pwd)"
 echo "Script running from $(pwd)"
 
 # resolve dirs
@@ -24,17 +24,11 @@
 export GRADLE_USER_HOME="$OUT_DIR/gradle"
 mkdir -p "$GRADLE_USER_HOME"
 
-if [ "$ROOT_DIR" == "" ]; then
-  ROOT_DIR="$WORKING_DIR"
-else
-  ROOT_DIR="$(cd $ROOT_DIR && pwd)"
-fi
-
-METALAVA_DIR=$ROOT_DIR/tools/metalava
+METALAVA_DIR=$CHECKOUT_ROOT/tools/metalava
 gw="$METALAVA_DIR/gradlew -Dorg.gradle.jvmargs=-Xmx24g"
 
 # Use androidx prebuilt since we don't have metalava prebuilts
-export ANDROID_HOME="$WORKING_DIR/../../prebuilts/fullsdk-linux/"
+export ANDROID_HOME="$CHECKOUT_ROOT/prebuilts/fullsdk-linux/"
 
 function buildMetalava() {
   METALAVA_BUILD_LOG="$OUT_DIR/metalava.log"
@@ -52,7 +46,7 @@
 
 # Mac grep doesn't support -P, so use perl version of `grep -oP "(?<=metalavaVersion=).*"`
 export METALAVA_VERSION=`perl -nle'print $& while m{(?<=metalavaVersion=).*}g' $METALAVA_DIR/src/main/resources/version.properties`
-export METALAVA_REPO="$ROOT_DIR/out/dist/repo/m2repository"
+export METALAVA_REPO="$CHECKOUT_ROOT/out/dist/repo/m2repository"
 
 function buildAndroidx() {
   ./frameworks/support/busytown/impl/build.sh $androidxArguments \
diff --git a/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java b/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java
index 329f07f..d60e6df 100644
--- a/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java
+++ b/car/app/app-projected/src/test/java/androidx/car/app/media/ProjectedCarAudioRecordTest.java
@@ -27,6 +27,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -48,6 +49,7 @@
         mCarContext.getFakeHost().setMicrophoneInputData(new ByteArrayInputStream(mArr));
     }
 
+    @Ignore // b/234034123
     @Test
     public void readNotStarted_throws() {
         byte[] arr = new byte[AUDIO_CONTENT_BUFFER_SIZE];
@@ -55,6 +57,7 @@
                 AUDIO_CONTENT_BUFFER_SIZE));
     }
 
+    @Ignore // b/234034123
     @Test
     public void readAfterStop_throws() {
         mCarAudioRecord.startRecording();
@@ -65,6 +68,7 @@
                 AUDIO_CONTENT_BUFFER_SIZE));
     }
 
+    @Ignore // b/234034123
     @Test
     public void read() {
         mCarAudioRecord.startRecording();
@@ -75,6 +79,7 @@
         mCarAudioRecord.stopRecording();
     }
 
+    @Ignore // b/234034123
     @Test
     public void readAfterAllRead_returns_negative_1() {
         mCarAudioRecord.startRecording();
@@ -87,6 +92,7 @@
         mCarAudioRecord.stopRecording();
     }
 
+    @Ignore // b/234034123
     @Test
     public void stopRecording_tellsHostToStop() {
         mCarAudioRecord.startRecording();
@@ -98,6 +104,7 @@
         assertThat(mCarContext.getFakeHost().hasToldHostToStopRecording()).isTrue();
     }
 
+    @Ignore // b/234034123
     @Test
     public void hostTellsToStop_noLongerReadsBytes() {
         mCarAudioRecord.startRecording();
diff --git a/datastore/datastore-core-okio/build.gradle b/datastore/datastore-core-okio/build.gradle
index 1e6cc67..11f42b4 100644
--- a/datastore/datastore-core-okio/build.gradle
+++ b/datastore/datastore-core-okio/build.gradle
@@ -58,6 +58,8 @@
                 api(libs.kotlinTestCommon)
                 api(libs.kotlinTestAnnotationsCommon)
                 api(libs.kotlinCoroutinesTest)
+                api(project(":internal-testutils-datastore"))
+                api(project(":internal-testutils-kmp"))
             }
         }
         jvmTest {
@@ -65,8 +67,11 @@
                 implementation(libs.kotlinCoroutinesTest)
                 implementation(libs.kotlinTest)
                 implementation(libs.kotlinTestAnnotationsCommon)
+                api(project(":internal-testutils-datastore"))
+                api(project(":internal-testutils-kmp"))
             }
         }
+
         if (enableNative) {
             nativeMain {
                 dependencies {
diff --git a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt
index 3a326cd..7ae42ea 100644
--- a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt
+++ b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/OkioStorageTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.datastore.core.okio
 
+import androidx.datastore.OkioTestIO
 import androidx.datastore.core.ReadScope
 import androidx.datastore.core.Storage
 import androidx.datastore.core.StorageConnection
@@ -23,6 +24,8 @@
 import androidx.datastore.core.readData
 import androidx.datastore.core.use
 import androidx.datastore.core.writeData
+import androidx.datastore.TestingOkioSerializer
+import androidx.datastore.TestingSerializerConfig
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,21 +46,21 @@
 class OkioStorageTest {
 
     private lateinit var testPath: Path
-    private lateinit var testingSerializer: TestingSerializer
+    private lateinit var testingSerializerConfig: TestingSerializerConfig
+    private lateinit var testingSerializer: TestingOkioSerializer
     private lateinit var testStorage: Storage<Byte>
     private lateinit var testConnection: StorageConnection<Byte>
     private lateinit var testScope: TestScope
-    private lateinit var fileScope: TestScope
-    private lateinit var testIO: TestIO
+    private lateinit var testIO: OkioTestIO
     private var fileSystem: FileSystem = FileSystem.SYSTEM
 
     @BeforeTest
     fun setUp() {
-        testIO = TestIO()
-        testingSerializer = TestingSerializer()
-        testPath = testIO.tempDir() / "temp-file.tmpo"
+        testIO = OkioTestIO()
+        testingSerializerConfig = TestingSerializerConfig()
+        testingSerializer = TestingOkioSerializer(testingSerializerConfig)
+        testPath = testIO.newTempFile().path
         testScope = TestScope(UnconfinedTestDispatcher())
-        fileScope = TestScope(UnconfinedTestDispatcher())
         testStorage = OkioStorage(fileSystem, testingSerializer) { testPath }
         testConnection = testStorage.createConnection()
     }
@@ -67,7 +70,7 @@
 
         val data = testConnection.readData()
 
-        assertThat(data).isEqualTo(0)
+        assertThat(data).isEqualTo(0.toByte())
     }
 
     @Test
@@ -179,7 +182,7 @@
 
         coroutineScope {
             testConnection.writeData(1)
-            testingSerializer.failingWrite = true
+            testingSerializerConfig.failingWrite = true
             assertThrows<IOException> { testConnection.writeData(1) }
         }
 
@@ -275,7 +278,8 @@
 
     @Test
     fun testWriteToNonExistentDir() = testScope.runTest {
-        val fileInNonExistentDir = testIO.tempDir() / "this/does/not/exist/foo.tst"
+        val fileInNonExistentDir = FileSystem.SYSTEM_TEMPORARY_DIRECTORY /
+            "this/does/not/exist/foo.tst"
         coroutineScope {
             val storage = OkioStorage(fileSystem, testingSerializer) { fileInNonExistentDir }
             storage.createConnection().use { connection ->
@@ -294,7 +298,7 @@
 
     @Test
     fun writeToDirFails() = testScope.runTest {
-        val directoryFile = testIO.tempDir() / "this/is/a/directory"
+        val directoryFile = FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "this/is/a/directory"
         fileSystem.createDirectories(directoryFile)
 
         val storage = OkioStorage(fileSystem, testingSerializer) { directoryFile }
@@ -310,7 +314,7 @@
             if (failFileProducer) {
                 throw IOException("Exception when producing file")
             }
-            testIO.tempDir() / "new-temp-file.tmp"
+            FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "new-temp-file.tmp"
         }
         val storage = OkioStorage(fileSystem, testingSerializer, fileProducer)
 
@@ -327,13 +331,13 @@
 
     @Test
     fun writeAfterTransientBadRead() = testScope.runTest {
-        testingSerializer.failingRead = true
+        testingSerializerConfig.failingRead = true
 
         testConnection.writeData(1)
 
         assertThrows<IOException> { testConnection.readData() }
 
-        testingSerializer.failingRead = false
+        testingSerializerConfig.failingRead = false
 
         testConnection.writeData(1)
         assertThat(testConnection.readData()).isEqualTo(1)
diff --git a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestIO.kt b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestIO.kt
deleted file mode 100644
index 9bcb6c8..0000000
--- a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestIO.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.datastore.core.okio
-
-import androidx.datastore.core.Storage
-import kotlin.random.Random
-import okio.FileSystem
-import okio.Path
-
-// TODO: move to its own module
-class TestIO(private val dirName: String = "datastore-test-dir") {
-    var onProduceFileCallback: () -> Unit = {}
-    private val fileSystem = FileSystem.SYSTEM
-    private fun randomFileName( // LAME :)
-        prefix: String = "test-file"
-    ): String {
-        return prefix + (0 until 15).joinToString(separator = "") {
-            ('a' + Random.nextInt(from = 0, until = 26)).toString()
-        }
-    }
-    private val tmpDir = tempDir()
-
-    fun tempDir(): Path {
-        return FileSystem.SYSTEM_TEMPORARY_DIRECTORY / randomFileName(dirName)
-    }
-
-    fun <T> newFileStorage(
-        serializer: OkioSerializer<T>,
-        prefix: String = "test-file"
-    ): Storage<T> {
-        val storage = OkioStorage(
-            fileSystem = fileSystem,
-            serializer = serializer
-        ) {
-            onProduceFileCallback()
-            tmpDir / randomFileName(prefix)
-        }
-        return storage
-    }
-
-    fun <T> newFileStorage(
-        serializer: OkioSerializer<T>,
-        testFile: Path
-    ): Storage<T> {
-        return OkioStorage(fileSystem, serializer) { testFile }
-    }
-}
\ No newline at end of file
diff --git a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestingSerializer.kt b/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestingSerializer.kt
deleted file mode 100644
index a120078..0000000
--- a/datastore/datastore-core-okio/src/commonTest/kotlin/androidx/datastore/core/okio/TestingSerializer.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.datastore.core.okio
-
-import androidx.datastore.core.CorruptionException
-import okio.BufferedSink
-import okio.BufferedSource
-import okio.EOFException
-import okio.IOException
-import okio.use
-
-internal class TestingSerializer(
-    var failReadWithCorruptionException: Boolean = false,
-    var failingRead: Boolean = false,
-    var failingWrite: Boolean = false,
-    override val defaultValue: Byte = 0
-) : OkioSerializer<Byte> {
-
-    override suspend fun readFrom(source: BufferedSource): Byte {
-        if (failReadWithCorruptionException) {
-            throw CorruptionException(
-                "CorruptionException",
-                IOException("I was asked to fail with corruption on reads")
-            )
-        }
-
-        if (failingRead) {
-            throw IOException("I was asked to fail on reads")
-        }
-
-        val read = try {
-            source.use {
-                it.readInt()
-            }
-        } catch (eof: EOFException) {
-            return 0
-        }
-        return read.toByte()
-    }
-
-    override suspend fun writeTo(t: Byte, sink: BufferedSink) {
-        if (failingWrite) {
-            throw IOException("I was asked to fail on writes")
-        }
-        sink.use {
-            it.writeInt(t.toInt())
-        }
-    }
-}
\ No newline at end of file
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index baaf645..4355020 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -55,6 +55,7 @@
                 implementation(libs.okio)
                 api(project(":datastore:datastore-core-okio"))
                 implementation(project(":internal-testutils-kmp"))
+                implementation(project(":internal-testutils-datastore"))
             }
         }
 
@@ -63,6 +64,7 @@
                 implementation(libs.junit)
                 implementation(libs.kotlinTest)
                 implementation(project(":internal-testutils-kmp"))
+                implementation(project(":internal-testutils-datastore"))
             }
         }
 
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt
index 1c63e8c..3fc0429 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CommonTests.kt
@@ -16,6 +16,8 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.OkioPath
+import androidx.datastore.OkioTestIO
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.InternalCoroutinesApi
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt
index d63ecc5..de8f95a 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/DataMigrationInitializerTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestFile
+import androidx.datastore.TestIO
+import androidx.datastore.TestingSerializerConfig
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import kotlin.test.BeforeTest
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
index 18cd4a0..420ab81 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
@@ -18,6 +18,9 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestFile
+import androidx.datastore.TestIO
+import androidx.datastore.TestingSerializerConfig
 import androidx.datastore.core.handlers.NoOpCorruptionHandler
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
diff --git a/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt b/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt
index 64f1d8a..76523eb 100644
--- a/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt
+++ b/datastore/datastore-core/src/jvmMain/java/androidx/datastore/core/FileStorage.kt
@@ -17,6 +17,7 @@
 package androidx.datastore.core
 
 import androidx.annotation.GuardedBy
+import androidx.annotation.RestrictTo
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -27,7 +28,15 @@
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 
-internal class FileStorage<T>(
+/**
+ * The Java IO File version of the Storage<T> interface. Is able to read and write T to a given
+ * file location.
+ *
+ * @param serializer The serializer that can write <T> to and from a byte array.
+ * @param produceFile The file producer that returns the file that will be read and written.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class FileStorage<T>(
     private val serializer: Serializer<T>,
     private val produceFile: () -> File
 ) : Storage<T> {
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt
index 7037e6d..f6dcfbc 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/DataStoreFactoryTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestingSerializerConfig
 import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
 import androidx.kruth.assertThat
 import java.io.File
@@ -71,7 +72,8 @@
 
         val store = DataStoreFactory.create(
             serializer = TestingSerializer(
-                TestingSerializerConfig(failReadWithCorruptionException = true)),
+                TestingSerializerConfig(failReadWithCorruptionException = true)
+            ),
             corruptionHandler = ReplaceFileCorruptionHandler<Byte> {
                 valueToReplace
             },
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt
index 72cf3fa..4cc1399 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileStorageTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestingSerializerConfig
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import java.io.File
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt
index e38dc1b..2910895 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/JvmTests.kt
@@ -16,6 +16,8 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.FileTestIO
+import androidx.datastore.JavaIOFile
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt
index 0e18d7f..abb75db 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt
+++ b/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/handlers/ReplaceFileCorruptionHandlerTest.kt
@@ -16,10 +16,10 @@
 
 package androidx.datastore.core.handlers
 
+import androidx.datastore.TestingSerializerConfig
 import androidx.datastore.core.FileStorage
 import androidx.datastore.core.SingleProcessDataStore
 import androidx.datastore.core.TestingSerializer
-import androidx.datastore.core.TestingSerializerConfig
 import androidx.kruth.assertThrows
 import androidx.kruth.assertThat
 import java.io.File
diff --git a/development/build_log_simplifier/message-flakes.ignore b/development/build_log_simplifier/message-flakes.ignore
index 0767997..f259723 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -22,11 +22,7 @@
 [0-9]+ test.*failed.*
 There were failing tests\. See the report at: .*.html
 # Some status messages
-Starting a Gradle Daemon, [0-9]+ busy and [0-9]+ incompatible Daemons could not be reused, use \-\-status for details
-Starting a Gradle Daemon, [0-9]+ busy and [0-9]+ incompatible and [0-9]+ stopped Daemons could not be reused, use \-\-status for details
-Starting a Gradle Daemon, [0-9]+ incompatible Daemon could not be reused, use \-\-status for details
-Starting a Gradle Daemon, [0-9]+ stopped Daemon could not be reused, use \-\-status for details
-Starting a Gradle Daemon \(subsequent builds will be faster\)
+Starting a Gradle Daemon.*
 Remote build cache is disabled when running with \-\-offline\.
 [0-9]+ actionable tasks: [0-9]+ up\-to\-date
 [0-9]+ actionable tasks: [0-9]+ executed
diff --git a/gradlew b/gradlew
index 2ba936b..3c2eea5 100755
--- a/gradlew
+++ b/gradlew
@@ -269,8 +269,7 @@
   if [ "$compact" == "--strict" ]; then
     expanded="-Pandroidx.validateNoUnrecognizedMessages\
      -Pandroidx.verifyUpToDate\
-     --no-watch-fs\
-     --no-daemon"
+     --no-watch-fs"
     if [ "$USE_ANDROIDX_REMOTE_BUILD_CACHE" == "" ]; then
       expanded="$expanded --offline"
     fi
diff --git a/metrics/integration-tests/janktest/src/main/AndroidManifest.xml b/metrics/integration-tests/janktest/src/main/AndroidManifest.xml
index 5a9eabd..3250f3b 100644
--- a/metrics/integration-tests/janktest/src/main/AndroidManifest.xml
+++ b/metrics/integration-tests/janktest/src/main/AndroidManifest.xml
@@ -34,6 +34,17 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".SimpleLoggingActivity"
+            android:label="@string/app_name"
+            android:exported="true"
+            android:theme="@style/Theme.Androidx.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".JankAggregatorActivity"
             android:label="@string/app_name"
             android:exported="true"
diff --git a/metrics/integration-tests/janktest/src/main/java/androidx/metrics/performance/janktest/SimpleLoggingActivity.kt b/metrics/integration-tests/janktest/src/main/java/androidx/metrics/performance/janktest/SimpleLoggingActivity.kt
new file mode 100644
index 0000000..ae5d51f
--- /dev/null
+++ b/metrics/integration-tests/janktest/src/main/java/androidx/metrics/performance/janktest/SimpleLoggingActivity.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.metrics.performance.janktest
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.metrics.performance.FrameData
+import androidx.metrics.performance.JankStats
+import androidx.metrics.performance.PerformanceMetricsState
+import java.util.Date
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+
+class SimpleLoggingActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.simple_layout)
+
+        JankStats.createAndTrack(window, Dispatchers.Default.asExecutor(),
+            object : JankStats.OnFrameListener {
+                override fun onFrame(frameData: FrameData) {
+                    Log.d("MainActivity", frameData.toString())
+                }
+            })
+
+        findViewById<View>(R.id.button).setOnClickListener {
+            val stateHolder = PerformanceMetricsState.getForHierarchy(it).state!!
+            stateHolder.addSingleFrameState("stateKey1", "stateValue")
+            stateHolder.addState("stateKey2", "${Date()}")
+        }
+    }
+}
\ No newline at end of file
diff --git a/metrics/integration-tests/janktest/src/main/res/layout/simple_layout.xml b/metrics/integration-tests/janktest/src/main/res/layout/simple_layout.xml
new file mode 100644
index 0000000..fd53e82
--- /dev/null
+++ b/metrics/integration-tests/janktest/src/main/res/layout/simple_layout.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Click Me"
+        android:id="@+id/button"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt b/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt
index e0774cc..88be2de 100644
--- a/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt
+++ b/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt
@@ -175,7 +175,7 @@
         decorView?.let {
             val frameStart = getFrameStartTime()
             with(decorView) {
-                handler.sendMessage(Message.obtain(handler) {
+                handler.sendMessageAtFrontOfQueue(Message.obtain(handler) {
                     val now = System.nanoTime()
                     val expectedDuration = getExpectedFrameDuration(decorView)
                     // prevent concurrent modification of delegates list by synchronizing on
diff --git a/paging/paging-common/src/main/kotlin/androidx/paging/Logger.kt b/paging/paging-common/src/main/kotlin/androidx/paging/Logger.kt
new file mode 100644
index 0000000..38cf905
--- /dev/null
+++ b/paging/paging-common/src/main/kotlin/androidx/paging/Logger.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+
+package androidx.paging
+import androidx.annotation.IntRange
+import androidx.annotation.RestrictTo
+
+/**
+ * Stores an instance of the Logger interface implementation which is to be injected by
+ * paging-runtime during runtime. This allows paging-common to add logs with
+ * android.util.log as a non-android artifact
+ */
+public var LOGGER: Logger? = null
+
+public const val LOG_TAG: String = "Paging"
+
+/**
+ * @hide
+ */
+public interface Logger {
+    public fun isLoggable(level: Int): Boolean
+    public fun log(level: Int, message: String, tr: Throwable? = null)
+}
+
+/**
+ * @hide
+ */
+public inline fun log(
+    @IntRange(from = VERBOSE.toLong(), to = DEBUG.toLong()) level: Int,
+    tr: Throwable? = null,
+    block: () -> String
+) {
+    val logger = LOGGER
+    if (logger?.isLoggable(level) == true) {
+        logger.log(level, block(), tr)
+    }
+}
+
+/**
+ * @hide
+ */
+public const val VERBOSE: Int = 2
+
+/**
+ * @hide
+ */
+public const val DEBUG: Int = 3
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index 2217df4..205bc66 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -16,6 +16,7 @@
 
 package androidx.paging
 
+import android.util.Log
 import androidx.annotation.IntRange
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
@@ -413,4 +414,36 @@
     fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
         differBase.removeLoadStateListener(listener)
     }
+
+    private companion object {
+        init {
+            /**
+             * Implements the Logger interface from paging-common and injects it into the LOGGER
+             * global var stored within Pager.
+             *
+             * Checks for null LOGGER because paging-compose can also inject a Logger
+             * with the same implementation
+             */
+            LOGGER = LOGGER ?: object : Logger {
+                override fun isLoggable(level: Int): Boolean {
+                    return Log.isLoggable(LOG_TAG, level)
+                }
+
+                override fun log(level: Int, message: String, tr: Throwable?) {
+                    when {
+                        tr != null && level == Log.DEBUG -> Log.d(LOG_TAG, message, tr)
+                        tr != null && level == Log.VERBOSE -> Log.v(LOG_TAG, message, tr)
+                        level == Log.DEBUG -> Log.d(LOG_TAG, message)
+                        level == Log.VERBOSE -> Log.v(LOG_TAG, message)
+                        else -> {
+                            throw IllegalArgumentException(
+                                "debug level $level is requested but Paging only supports " +
+                                    "default logging for level 2 (DEBUG) or level 3 (VERBOSE)"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/room/room-common/src/main/java/androidx/room/Junction.kt b/room/room-common/src/main/java/androidx/room/Junction.kt
index 1f114b7..5ea5ef4 100644
--- a/room/room-common/src/main/java/androidx/room/Junction.kt
+++ b/room/room-common/src/main/java/androidx/room/Junction.kt
@@ -53,7 +53,7 @@
  * }
  * ```
  *
- * In the above example the many-to-many relationship between `Song` and `Playlist` has
+ * In the above example the many-to-many relationship between a `Song` and a `Playlist` has
  * an associative table defined by the entity `PlaylistSongXRef`.
  *
  * @see [Relation]
diff --git a/settings.gradle b/settings.gradle
index 58fa787..23ede6a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -951,7 +951,7 @@
 /////////////////////////////
 
 includeProject(":internal-testutils-common", "testutils/testutils-common", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":internal-testutils-datastore", "testutils/testutils-datastore", [BuildType.MAIN])
+includeProject(":internal-testutils-datastore", "testutils/testutils-datastore", [BuildType.MAIN, BuildType.KMP])
 includeProject(":internal-testutils-runtime", "testutils/testutils-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.MEDIA, BuildType.WEAR, BuildType.CAMERA])
 includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN])
 includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index 6582467..0888962 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -18,11 +18,32 @@
 
 plugins {
     id("AndroidXPlugin")
-    id("kotlin")
 }
+androidXMultiplatform {
+    jvm {}
+    macosX64()
+    linuxX64()
+    macosArm64()
 
-dependencies {
-    implementation(libs.kotlinStdlib)
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(project(":datastore:datastore-core"))
+                api(project(":datastore:datastore-core-okio"))
+                api(libs.kotlinStdlibCommon)
+                api(libs.kotlinTestCommon)
+                api(libs.kotlinCoroutinesCore)
+                api(libs.okio)
+            }
+        }
+
+        jvmMain {
+            dependencies {
+                api(libs.kotlinStdlib)
+                api(libs.kotlinTest)
+            }
+        }
+    }
 }
 
 androidx {
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/OkioTestIO.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/OkioTestIO.kt
similarity index 97%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/OkioTestIO.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/OkioTestIO.kt
index 3244e85..afde3af 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/OkioTestIO.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/OkioTestIO.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.Storage
 import androidx.datastore.core.okio.OkioStorage
 import kotlin.random.Random
 import kotlin.reflect.KClass
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestIO.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestIO.kt
similarity index 86%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestIO.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestIO.kt
index 94de2b6..772f901 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestIO.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestIO.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.Storage
+import androidx.datastore.core.DataStoreFactory.create
 import kotlin.reflect.KClass
 import kotlinx.coroutines.CoroutineScope
 
-// TODO(b/237677833): move this class when datastore test utils is created
 abstract class TestIO<F : TestFile, IOE : Throwable>(
     protected val dirName: String = "datastore-test-dir"
 ) {
@@ -29,7 +31,7 @@
         scope: CoroutineScope,
         futureFile: () -> TestFile
     ): DataStore<Byte> {
-        return SingleProcessDataStore(getStorage(serializerConfig, futureFile), scope = scope)
+        return create(getStorage(serializerConfig, futureFile), scope = scope)
     }
 
     abstract fun getStorage(
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingOkioSerializer.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingOkioSerializer.kt
similarity index 91%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingOkioSerializer.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingOkioSerializer.kt
index 82885ac..cdfff76 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingOkioSerializer.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingOkioSerializer.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.CorruptionException
 import androidx.datastore.core.okio.OkioSerializer
 import okio.BufferedSink
 import okio.BufferedSource
@@ -23,8 +24,7 @@
 import okio.IOException
 import okio.use
 
-// TODO(b/237677833): remove this class when datastore test utils is created
-internal class TestingOkioSerializer(
+class TestingOkioSerializer(
     val config: TestingSerializerConfig
 ) : OkioSerializer<Byte> {
 
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingSerializerConfig.kt
similarity index 96%
rename from datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
rename to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingSerializerConfig.kt
index 3bd6754..2ea35c5 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/TestingSerializerConfig.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/TestingSerializerConfig.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
 import kotlin.jvm.Volatile
 
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileTestIO.kt b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileTestIO.kt
similarity index 88%
rename from datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileTestIO.kt
rename to testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileTestIO.kt
index 00ca26b6..3a2c5d0 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/FileTestIO.kt
+++ b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileTestIO.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package androidx.datastore.core
+package androidx.datastore
 
+import androidx.datastore.core.FileStorage
+import androidx.datastore.core.Storage
+import androidx.datastore.core.TestingSerializer
 import java.io.File
 import java.io.IOException
 import kotlin.reflect.KClass
@@ -43,9 +46,9 @@
         serializerConfig: TestingSerializerConfig,
         futureFile: () -> TestFile
     ): Storage<Byte> {
-        return FileStorage(
-            TestingSerializer(serializerConfig)
-        ) { (futureFile() as JavaIOFile).file }
+        return FileStorage(TestingSerializer(serializerConfig)) {
+            (futureFile() as JavaIOFile).file
+        }
     }
 
     override fun isDirectory(file: JavaIOFile): Boolean {
diff --git a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/TestingSerializer.kt b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/TestingSerializer.kt
similarity index 95%
rename from datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/TestingSerializer.kt
rename to testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/TestingSerializer.kt
index 9069e0c..fcfc44b 100644
--- a/datastore/datastore-core/src/jvmTest/java/androidx/datastore/core/TestingSerializer.kt
+++ b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/TestingSerializer.kt
@@ -16,11 +16,12 @@
 
 package androidx.datastore.core
 
+import androidx.datastore.TestingSerializerConfig
 import java.io.IOException
 import java.io.InputStream
 import java.io.OutputStream
 
-internal class TestingSerializer(
+class TestingSerializer(
     val config: TestingSerializerConfig = TestingSerializerConfig(),
 ) : Serializer<Byte> {
     override suspend fun readFrom(input: InputStream): Byte {