AndroidBenchmarkRunner.kt

/*
 * Copyright 2019 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.junit4

import androidx.annotation.CallSuper
import androidx.benchmark.IsolationActivity
import androidx.test.runner.AndroidJUnitRunner

/**
 * Instrumentation runner for benchmarks, used to increase stability of measurements and minimize
 * interference.
 *
 * To use this runner, put the following in your module level `build.gradle`:
 *
 * ```
 * android {
 *     defaultConfig {
 *         testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner"
 *     }
 * }
 * ```
 *
 * ## Minimizing Interference
 *
 * This runner launches a simple opaque activity used to reduce benchmark interference from other
 * windows. Launching other activities is supported e.g. via ActivityTestRule and ActivityScenario -
 * the opaque activity will be relaunched if not actively running before each test, and after each
 * test's cleanup is complete.
 *
 * For example, sources of potential interference:
 * - live wallpaper rendering
 * - homescreen widget updates
 * - hotword detection
 * - status bar repaints
 * - running in background (some cores may be foreground-app only)
 *
 * ## Clock Stability
 *
 * While it is better for performance stability to lock clocks with the `./gradlew lockClocks` task
 * provided by the gradle plugin, this is not possible on most devices. The runner provides a
 * fallback mode for preventing thermal throttling.
 *
 * On devices that support [android.view.Window.setSustainedPerformanceMode], the runner will set
 * this mode on the window of every Activity launched (including the opaque Activity mentioned
 * above). The runner will also launch a continuously spinning Thread. Together, these ensure that
 * the app runs in the multithreaded stable performance mode, which locks the maximum clock
 * frequency to prevent thermal throttling. This ensures stable clock levels across all benchmarks,
 * even if a continuous suite of benchmarks runs for many minutes on end.
 */
@Suppress("unused") // Note: not referenced by code
public open class AndroidBenchmarkRunner : AndroidJUnitRunner() {

    @CallSuper
    override fun waitForActivitiesToComplete() {
        // We don't call the super method here, since we have
        // an activity we intend to persist between tests
        // TODO: somehow wait for every activity but IsolationActivity

        // Before/After each test, from the test thread, synchronously launch
        // our IsolationActivity if it's not already resumed
        var isResumed = false
        runOnMainSync {
            isResumed = IsolationActivity.resumed
        }
        if (!isResumed) {
            IsolationActivity.launchSingleton()
        }
    }

    @CallSuper
    override fun onDestroy() {
        IsolationActivity.finishSingleton()
        super.waitForActivitiesToComplete()
        super.onDestroy()
    }
}