Merge changes from topic "safeoutputs" into androidx-main

* changes:
  Rework file output for safety, dont swallow throwables
  Fix file / trace file moving
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
index 5c58edc..99f775b 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ArgumentInjectingApplication.kt
@@ -52,6 +52,10 @@
                 "ACTIVITY-MISSING,CODE-COVERAGE,DEBUGGABLE,EMULATOR,LOW-BATTERY,UNLOCKED," +
                     "UNSUSTAINED-ACTIVITY-MISSING,ENG-BUILD"
             )
+            putString(
+                "androidx.benchmark.thermalThrottle.sleepDurationSeconds",
+                "0"
+            )
             // TODO: consider moving default directory to files dir.
             putString(
                 "additionalTestOutputDir",
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/OutputsTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/OutputsTest.kt
index 02322c9..1ffe3b6 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/OutputsTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/OutputsTest.kt
@@ -19,14 +19,15 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
 import java.io.File
 import java.util.Date
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
+import org.junit.Assert
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -105,6 +106,7 @@
     public fun dirUsableByAppAndShell_writeAppReadShell() {
         val dir = Outputs.dirUsableByAppAndShell
         val file = File.createTempFile("testFile", null, dir)
+        file.setReadable(true, false)
         try {
             file.writeText(file.name) // use name, as it's fairly unique
             Assert.assertEquals(
@@ -115,4 +117,70 @@
             file.delete()
         }
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public fun dirUsableByAppAndShell_writeShellReadShell() {
+        val dir = Outputs.dirUsableByAppAndShell
+
+        // simple way to get a unique path, not shared across runs
+        val file = File.createTempFile("shellwrite", null, dir)
+        val path = file.absolutePath
+        file.delete()
+
+        Shell.executeScriptSilent("rm -f $path")
+        try {
+            Shell.executeScriptSilent("echo test > $path")
+            assertEquals(
+                "test\n",
+                Shell.executeScriptCaptureStdout("cat $path")
+            )
+            file.appendBytes("extra".toByteArray())
+        } finally {
+            Shell.executeScriptSilent("rm -f $path")
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public fun dirUsableByAppAndShell_writeShellReadApp() {
+        val dir = Outputs.dirUsableByAppAndShell
+
+        // simple way to get a unique path, not shared across runs
+        val file = File.createTempFile("shellwrite", null, dir)
+        val path = file.absolutePath
+        file.delete()
+
+        Shell.executeScriptSilent("rm -f $path")
+        try {
+            Shell.executeScriptSilent("echo test > $path")
+            assertEquals(
+                "test\n",
+                File(path).readText()
+            )
+            file.appendBytes("extra".toByteArray())
+        } finally {
+            Shell.executeScriptSilent("rm -f $path")
+        }
+    }
+
+    /**
+     * NOTE: this test checks that the instrumentation argument additionalTestOutputDir isn't set to
+     * an invalid / unusable location.
+     *
+     * Running through Studio/Gradle, this isn't defined by the library, it's defined by AGP.
+     *
+     * If this test fails, we need to handle the directory differently.
+     */
+    @Test
+    fun additionalTestOutputDir_appWrite() {
+        val additionalTestOutputDir = Arguments.additionalTestOutputDir
+        assumeTrue(additionalTestOutputDir != null)
+        val file = File.createTempFile("testFile", null, File(additionalTestOutputDir!!))
+        try {
+            file.writeText("testString")
+        } finally {
+            file.delete()
+        }
+    }
 }
\ No newline at end of file
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index aa98691..fbbbc32 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -76,6 +76,7 @@
         get() = if (profilerOverride != null) profilerOverride else _profiler
     internal val profilerSampleFrequency: Int
     internal val profilerSampleDurationSeconds: Long
+    internal val thermalThrottleSleepDurationSeconds: Long
 
     internal var error: String? = null
     internal val additionalTestOutputDir: String?
@@ -164,6 +165,12 @@
                     "$profilerSampleFrequency, duration $profilerSampleDurationSeconds"
             )
         }
+
+        thermalThrottleSleepDurationSeconds =
+            arguments.getBenchmarkArgument("thermalThrottle.sleepDurationSeconds")?.ifBlank { null }
+                ?.toLong()
+                ?: 90
+
         additionalTestOutputDir = arguments.getString("additionalTestOutputDir")
         Log.d(BenchmarkState.TAG, "additionalTestOutputDir=$additionalTestOutputDir")
 
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
index b0dc302..fa768fc 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -621,7 +621,7 @@
         internal const val MIN_TEST_ITERATIONS = 1
 
         private const val THROTTLE_MAX_RETRIES = 2
-        private const val THROTTLE_BACKOFF_S = 90L
+        private val THROTTLE_BACKOFF_S = Arguments.thermalThrottleSleepDurationSeconds
 
         private var firstBenchmark = true
 
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
index 3865c5c..246ca42 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
@@ -42,14 +42,22 @@
     /**
      * The usable output directory, given permission issues with `adb shell` on Android R.
      * Both the app and the shell have access to this output folder.
+     *
+     * This dir can be read/written by app
+     * This dir can be read by shell (see [forceFilesForShellAccessible] for API 21/22!)
      */
     public val dirUsableByAppAndShell: File
 
+    /**
+     * Any file created by this process for the shell to use must be explicitly made filesystem
+     * globally readable, as prior to API 23 the shell didn't have access by default.
+     */
+    val forceFilesForShellAccessible: Boolean = Build.VERSION.SDK_INT in 21..22
+
     init {
         // Be explicit about the TimeZone for stable formatting
         formatter.timeZone = TimeZone.getTimeZone("UTC")
 
-        @Suppress("DEPRECATION")
         @SuppressLint("NewApi")
         dirUsableByAppAndShell = when {
             Build.VERSION.SDK_INT >= 29 -> {
@@ -70,12 +78,21 @@
                 "additionalTestOutputDir argument required to declare output dir."
         )
 
+        if (forceFilesForShellAccessible) {
+            // By default, shell doesn't have access to app dirs on 21/22 so we need to modify
+            // this so that the shell can output here too
+            dirUsableByAppAndShell.setReadable(true, false)
+            dirUsableByAppAndShell.setWritable(true, false)
+            dirUsableByAppAndShell.setExecutable(true, false)
+        }
+
         Log.d(BenchmarkState.TAG, "Usable output directory: $dirUsableByAppAndShell")
 
         outputDirectory = Arguments.additionalTestOutputDir?.let { File(it) }
             ?: dirUsableByAppAndShell
 
         Log.d(BenchmarkState.TAG, "Output Directory: $outputDirectory")
+        outputDirectory.mkdirs()
     }
 
     /**
@@ -93,47 +110,27 @@
         block: (file: File) -> Unit,
     ): String {
         val sanitizedName = sanitizeFilename(fileName)
+        val destination = File(outputDirectory, sanitizedName)
 
-        // We need to copy files over anytime `dirUsableByAppAndShell` is different from
-        // `outputDirectory`.
-        val override = dirUsableByAppAndShell != outputDirectory
         // We override the `additionalTestOutputDir` argument.
         // Context: b/181601156
         val file = File(dirUsableByAppAndShell, sanitizedName)
-        try {
-            block.invoke(file)
-        } finally {
-            var destination = file
-            if (override) {
-                // This respects the `additionalTestOutputDir` argument.
-                val actualOutputDirectory = outputDirectory
-                destination = File(actualOutputDirectory, sanitizedName)
-                Log.d(BenchmarkState.TAG, "Copying $file to $destination")
-                try {
-                    destination.mkdirs()
-                    file.copyTo(destination, overwrite = true)
-                } catch (exception: Throwable) {
-                    // This can happen when `additionalTestOutputDir` being passed in cannot
-                    // be written to. The shell does not have permissions to do the necessary
-                    // setup, and this can cause `adb pull` to fail.
-                    val message = """
-                        Unable to copy files to ${destination.absolutePath}.
-                        Please pull the Macrobenchmark results manually by using:
-                        adb pull ${file.absolutePath}
-                    """.trimIndent()
-                    Log.e(BenchmarkState.TAG, message, exception)
+        block.invoke(file)
+        check(file.exists()) { "File doesn't exist!" }
 
-                    // TODO(b/227510293): return failure/null to signal file isn't readable
-                    destination = file
-                }
-            }
-            InstrumentationResults.reportAdditionalFileToCopy(
-                key = reportKey,
-                absoluteFilePath = destination.absolutePath,
-                reportOnRunEndOnly = reportOnRunEndOnly
-            )
-            return destination.absolutePath
+        if (dirUsableByAppAndShell != outputDirectory) {
+            // We need to copy files over anytime `dirUsableByAppAndShell` is different from
+            // `outputDirectory`.
+            Log.d(BenchmarkState.TAG, "Copying $file to $destination")
+            file.copyTo(destination, overwrite = true)
         }
+
+        InstrumentationResults.reportAdditionalFileToCopy(
+            key = reportKey,
+            absoluteFilePath = destination.absolutePath,
+            reportOnRunEndOnly = reportOnRunEndOnly
+        )
+        return destination.absolutePath
     }
 
     public fun sanitizeFilename(filename: String): String {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
index 0f0b95f..b0b4ad3 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
@@ -119,9 +119,8 @@
             ShellImpl.executeCommandUnsafe("md5sum $path").substringBefore(" ")
         } else {
             // this isn't good, but it's good enough for API 22
-            val out = ShellImpl.executeCommandUnsafe("ls -l $path").split(Regex("\\s+"))[3]
-            println("value is $out")
-            out
+            val result = ShellImpl.executeCommandUnsafe("ls -l $path")
+            if (result.isBlank()) "" else result.split(Regex("\\s+"))[3]
         }
         check(sum.isNotBlank()) {
             "Checksum for $path was blank"
@@ -169,11 +168,10 @@
     fun createRunnableExecutable(name: String, inputStream: InputStream): String {
         // dirUsableByAppAndShell is writable, but we can't execute there (as of Q),
         // so we copy to /data/local/tmp
-        val externalDir = Outputs.dirUsableByAppAndShell
         val writableExecutableFile = File.createTempFile(
             /* prefix */ "temporary_$name",
             /* suffix */ null,
-            /* directory */ externalDir
+            /* directory */ Outputs.dirUsableByAppAndShell
         )
         val runnableExecutablePath = "/data/local/tmp/$name"
 
@@ -181,6 +179,11 @@
             writableExecutableFile.outputStream().use {
                 inputStream.copyTo(it)
             }
+            if (Outputs.forceFilesForShellAccessible) {
+                // executable must be readable by shell to be moved, and for some reason
+                // doesn't inherit shell readability from dirUsableByAppAndShell
+                writableExecutableFile.setReadable(true, false)
+            }
             moveToTmpAndMakeExecutable(
                 src = writableExecutableFile.absolutePath,
                 dst = runnableExecutablePath
@@ -552,6 +555,12 @@
         val externalDir = Outputs.dirUsableByAppAndShell
         val scriptContentFile = File.createTempFile("temporaryScript", null, externalDir)
 
+        if (Outputs.forceFilesForShellAccessible) {
+            // script content must be readable by shell, and for some reason doesn't
+            // inherit shell readability from dirUsableByAppAndShell
+            scriptContentFile.setReadable(true, false)
+        }
+
         // only create/read/delete stdin/stderr files if they are needed
         val stdinFile = stdin?.run {
             File.createTempFile("temporaryStdin", null, externalDir)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
index 163a308..8cc6dde 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
@@ -72,6 +72,9 @@
                     listOf("*")
                 }
                 configProtoFile.writeBytes(perfettoConfig(atraceApps).validateAndEncode())
+                if (Outputs.forceFilesForShellAccessible) {
+                    configProtoFile.setReadable(true, /* ownerOnly = */ false)
+                }
             }
             userspaceTrace("start perfetto process") {
                 helper.startCollecting(configProtoFile.absolutePath, false)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
index 198ab7a..1ffbb6a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
@@ -23,6 +23,7 @@
 import androidx.benchmark.Outputs
 import androidx.benchmark.Outputs.dateToFileName
 import androidx.benchmark.PropOverride
+import androidx.benchmark.Shell
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 
 /**
@@ -80,6 +81,12 @@
             reportKey = "perfetto_trace_$traceLabel"
         ) {
             capture!!.stop(it.absolutePath)
+            if (Outputs.forceFilesForShellAccessible) {
+                // This shell written file must be made readable to be later accessed by this
+                // process (e.g. for appending UiState). Unlike in other places, shell
+                // must increase access, since it's giving the app access
+                Shell.executeScriptSilent("chmod 777 ${it.absolutePath}")
+            }
         }
     }
 
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index 01381a9..e3a3d72 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -97,18 +97,20 @@
                 val path = "$UNBUNDLED_PERFETTO_ROOT_DIR/config.pb"
                 // Move the config to a directory that unbundled perfetto has permissions for.
                 Shell.executeScriptSilent("rm -f $path")
-                if (Build.VERSION.SDK_INT >= 24) {
-                    Shell.executeScriptSilent("mv $configFilePath $path")
-                } else {
+                if (Build.VERSION.SDK_INT == 23) {
                     // Observed stderr output (though command still completes successfully) on:
                     // google/shamu/shamu:6.0.1/MOB31T/3671974:userdebug/dev-keys
                     // Doesn't repro on all API 23 devices :|
-                    Shell.executeScriptCaptureStdoutStderr("mv $configFilePath $path").also {
+                    Shell.executeScriptCaptureStdoutStderr("cp $configFilePath $path").also {
                         check(
                             it.stdout.isBlank() &&
                                 (it.stderr.isBlank() || it.stderr.startsWith("mv: chown"))
-                        )
+                        ) {
+                            "Observed unexpected output: it"
+                        }
                     }
+                } else {
+                    Shell.executeScriptSilent("cp $configFilePath $path")
                 }
                 path
             } else {
@@ -116,8 +118,15 @@
             }
 
             val outputPath = getPerfettoTmpOutputFilePath()
+
+            if (!unbundled && Build.VERSION.SDK_INT == 29) {
+                // observed this on unrooted emulator
+                val output = Shell.executeScriptCaptureStdoutStderr("rm -f $outputPath")
+                Log.d(LOG_TAG, "Attempted to remove $outputPath, result = $output")
+            } else {
+                Shell.executeScriptSilent("rm -f $outputPath")
+            }
             // Remove already existing temporary output trace file if any.
-            Shell.executeScriptSilent("rm -f $outputPath")
 
             // Perfetto
             val perfettoCmd = perfettoCommand(actualConfigPath, isTextProtoConfig)
@@ -309,6 +318,7 @@
     private fun copyFileOutput(destinationFile: String): Boolean {
         val sourceFile = getPerfettoTmpOutputFilePath()
         val filePath = File(destinationFile)
+        filePath.setWritable(true, false)
         val destDirectory = filePath.parent
         if (destDirectory != null) {
             // Check if the directory already exists
@@ -328,14 +338,14 @@
         // Copy the collected trace from /data/misc/perfetto-traces/trace_output.pb to
         // destinationFile
         try {
-            val moveResult =
-                Shell.executeScriptCaptureStdoutStderr("mv $sourceFile $destinationFile")
-            if (!moveResult.isBlank()) {
+            val copyResult =
+                Shell.executeScriptCaptureStdoutStderr("cp $sourceFile $destinationFile")
+            if (!copyResult.isBlank()) {
                 Log.e(
                     LOG_TAG,
                     """
-                        Unable to move perfetto output file from $sourceFile
-                        to $destinationFile due to $moveResult.
+                        Unable to copy perfetto output file from $sourceFile
+                        to $destinationFile due to $copyResult.
                     """.trimIndent()
                 )
                 return false
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt
index c9949cc..207731a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt
@@ -16,12 +16,8 @@
 
 package androidx.benchmark.perfetto
 
-import android.os.Build
-import android.util.Log
 import androidx.annotation.RestrictTo
-import androidx.benchmark.BenchmarkState.Companion.TAG
 import java.io.File
-import java.io.FileNotFoundException
 import perfetto.protos.Trace
 import perfetto.protos.TracePacket
 import perfetto.protos.UiState
@@ -45,19 +41,5 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun File.appendUiState(state: UiState) {
     val traceToAppend = Trace(packet = listOf(TracePacket(ui_state = state)))
-    appendBytesSafely(traceToAppend.encode())
-}
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-fun File.appendBytesSafely(bytes: ByteArray) {
-    try {
-        appendBytes(bytes)
-    } catch (e: FileNotFoundException) {
-        if (Build.VERSION.SDK_INT in 21..22) {
-            // Failure is common on API 21/22 due to b/227510293
-            Log.d(TAG, "Unable to append additional bytes to ${this.absolutePath}")
-        } else {
-            throw e
-        }
-    }
+    appendBytes(traceToAppend.encode())
 }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/simpleperf/ProfileSession.java b/benchmark/benchmark-common/src/main/java/androidx/benchmark/simpleperf/ProfileSession.java
index 3305363..e50a95a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/simpleperf/ProfileSession.java
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/simpleperf/ProfileSession.java
@@ -154,7 +154,7 @@
         mSimpleperfPath = findSimpleperf();
         checkIfPerfEnabled();
         createSimpleperfDataDir();
-        createSimpleperfProcess(mSimpleperfPath, args);
+        startRecordingProcess(args);
         mState = State.STARTED;
     }
 
@@ -327,7 +327,7 @@
 
     /**
      * Convert a .data simpleperf file to the .trace proto format.
-     *
+     * <p>
      * Paths may be relative to the simpleperf data dir.
      *
      * @param inputPath Path of the .data file within the simpleperf output directory
@@ -338,6 +338,7 @@
             @NonNull String outputPath
     ) {
         ArrayList<String> args = new ArrayList<>();
+        args.add(mSimpleperfPath);
         args.add("report-sample");
         args.add("--protobuf");
         args.add("--show-callchain");
@@ -346,14 +347,23 @@
         args.add("-o");
         args.add(outputPath);
 
-        createSimpleperfProcess(mSimpleperfPath, args);
+        createProcess(args);
         waitForSimpleperfProcess();
     }
 
-    private void createSimpleperfProcess(String simpleperfPath, List<String> recordArgs) {
+    private void createProcess(List<String> args) {
+        ProcessBuilder pb = new ProcessBuilder(args).directory(new File(mSimpleperfDataDir));
+        try {
+            mSimpleperfProcess = pb.start();
+        } catch (IOException e) {
+            throw new Error("failed to create simpleperf process: " + e.getMessage());
+        }
+    }
+
+    private void startRecordingProcess(List<String> recordArgs) {
         // 1. Prepare simpleperf arguments.
         ArrayList<String> args = new ArrayList<>();
-        args.add(simpleperfPath);
+        args.add(mSimpleperfPath);
         args.add("record");
         args.add("--log-to-android-buffer");
         args.add("--log");
@@ -365,12 +375,7 @@
         args.addAll(recordArgs);
 
         // 2. Create the simpleperf process.
-        ProcessBuilder pb = new ProcessBuilder(args).directory(new File(mSimpleperfDataDir));
-        try {
-            mSimpleperfProcess = pb.start();
-        } catch (IOException e) {
-            throw new Error("failed to create simpleperf process: " + e.getMessage());
-        }
+        createProcess(args);
 
         // 3. Wait until simpleperf starts recording.
         String startFlag = readReply();
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
index 1671a9b..1be0375 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
@@ -24,7 +24,6 @@
 import androidx.benchmark.UserspaceTracing
 import androidx.benchmark.perfetto.PerfettoCaptureWrapper
 import androidx.benchmark.perfetto.UiState
-import androidx.benchmark.perfetto.appendBytesSafely
 import androidx.benchmark.perfetto.appendUiState
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
@@ -224,9 +223,9 @@
                 // that events won't lie outside the bounds of the trace content.
                 userspaceTrace = UserspaceTracing.commitToTrace()
             }?.apply {
-                // trace completed, and copied into app writeable dir
+                // trace completed, and copied into shell writeable dir
                 val file = File(this)
-                file.appendBytesSafely(userspaceTrace!!.encode())
+                file.appendBytes(userspaceTrace!!.encode())
                 file.appendUiState(
                     UiState(
                         timelineStart = null,