Shell.kt
/*
* Copyright 2021 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
import android.os.Build
import android.os.Looper
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.test.platform.app.InstrumentationRegistry
import java.nio.charset.Charset
/**
* Shell command helpers, which no-op below API 21
*
* Eventually, ShellUtils in macrobenchmark should likely merge into this.
*
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object Shell {
fun connectUiAutomation() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ShellImpl // force initialization
}
}
/**
* Run a command, and capture stdout
*
* Below L, returns null
*/
fun optionalCommand(command: String): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ShellImpl.executeShellCommand(command)
} else {
null
}
}
/**
* Function for reading shell-accessible proc files, like scaling_max_freq, which can't be
* read directly by the app process.
*/
fun catProcFileLong(path: String): Long? {
return optionalCommand("cat $path")
?.trim()
?.run {
try {
toLong()
} catch (exception: NumberFormatException) {
// silently catch exception, as it may be not readable (e.g. due to offline)
null
}
}
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private object ShellImpl {
init {
require(Looper.getMainLooper().thread != Thread.currentThread()) {
"ShellImpl must not be initialized on the UI thread - UiAutomation must not be " +
"connected on the main thread!"
}
}
private val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
/**
* Reimplementation of UiAutomator's Device.executeShellCommand,
* to avoid the UiAutomator dependency
*/
fun executeShellCommand(cmd: String): String {
val parcelFileDescriptor = uiAutomation.executeShellCommand(cmd)
AutoCloseInputStream(parcelFileDescriptor).use { inputStream ->
return inputStream.readBytes().toString(Charset.defaultCharset())
}
}
}