1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.benchmark
18 
19 import android.app.Activity
20 import android.app.Application
21 import android.content.Intent
22 import android.os.Build
23 import android.os.Bundle
24 import android.os.Process
25 import android.util.Log
26 import android.view.WindowManager
27 import android.widget.TextView
28 import androidx.annotation.AnyThread
29 import androidx.annotation.RestrictTo
30 import androidx.annotation.WorkerThread
31 import androidx.test.platform.app.InstrumentationRegistry
32 import java.util.concurrent.atomic.AtomicReference
33 import kotlin.concurrent.thread
34 
35 /**
36  * Simple opaque activity used to reduce benchmark interference from other windows.
37  *
38  * For example, sources of potential interference:
39  * - live wallpaper rendering
40  * - homescreen widget updates
41  * - hotword detection
42  * - status bar repaints
43  * - running in background (some cores may be foreground-app only)
44  */
45 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
46 public class IsolationActivity : android.app.Activity() {
47     private var destroyed = false
48 
onCreatenull49     override fun onCreate(savedInstanceState: Bundle?) {
50         super.onCreate(savedInstanceState)
51         setContentView(R.layout.isolation_activity)
52 
53         // disable launch animation
54         @Suppress("Deprecation") overridePendingTransition(0, 0)
55 
56         if (firstInit) {
57             if (!CpuInfo.locked && isSustainedPerformanceModeSupported()) {
58                 sustainedPerformanceModeInUse = true
59             }
60             application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
61 
62             // trigger the one missed lifecycle event, from registering the callbacks late
63             activityLifecycleCallbacks.onActivityCreated(this, savedInstanceState)
64 
65             if (sustainedPerformanceModeInUse) {
66                 // Keep at least one core busy. Together with a single threaded benchmark, this
67                 // makes the process get multi-threaded setSustainedPerformanceMode.
68                 //
69                 // We want to keep to the relatively lower clocks of the multi-threaded benchmark
70                 // mode to avoid any benchmarks running at higher clocks than any others.
71                 //
72                 // Note, thread names have 15 char max in Systrace
73                 thread(name = "BenchSpinThread") {
74                     Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST)
75                     while (true) {}
76                 }
77             }
78             firstInit = false
79         }
80 
81         val old = singleton.getAndSet(this)
82         if (old != null && !old.destroyed && !old.isFinishing) {
83             throw IllegalStateException("Only one IsolationActivity should exist")
84         }
85 
86         findViewById<TextView>(R.id.clock_state).text =
87             when {
88                 CpuInfo.locked -> "Locked Clocks"
89                 sustainedPerformanceModeInUse -> "Sustained Performance Mode"
90                 else -> ""
91             }
92     }
93 
onResumenull94     override fun onResume() {
95         super.onResume()
96         resumed = true
97     }
98 
onPausenull99     override fun onPause() {
100         super.onPause()
101         resumed = false
102     }
103 
onDestroynull104     override fun onDestroy() {
105         super.onDestroy()
106         destroyed = true
107     }
108 
109     /** finish is ignored! we defer until [actuallyFinish] is called. */
finishnull110     override fun finish() {}
111 
actuallyFinishnull112     public fun actuallyFinish() {
113         // disable close animation
114         @Suppress("Deprecation") overridePendingTransition(0, 0)
115         super.finish()
116     }
117 
118     public companion object {
119         private const val TAG = "Benchmark"
120         internal val singleton = AtomicReference<IsolationActivity>()
121         private var firstInit = true
122         internal var sustainedPerformanceModeInUse = false
123             private set
124 
125         public var resumed: Boolean = false
126             private set
127 
128         @WorkerThread
launchSingletonnull129         public fun launchSingleton() {
130             val intent =
131                 Intent(Intent.ACTION_MAIN).apply {
132                     Log.d(TAG, "launching Benchmark IsolationActivity")
133                     setClassName(
134                         InstrumentationRegistry.getInstrumentation().targetContext.packageName,
135                         IsolationActivity::class.java.name
136                     )
137                     addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
138                     addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
139                 }
140             InstrumentationRegistry.getInstrumentation().startActivitySync(intent)
141         }
142 
143         @AnyThread
finishSingletonnull144         public fun finishSingleton() {
145             Log.d(TAG, "Benchmark runner being destroyed, tearing down activities")
146             singleton.getAndSet(null)?.apply { runOnUiThread { actuallyFinish() } }
147         }
148 
isSustainedPerformanceModeSupportednull149         internal fun isSustainedPerformanceModeSupported(): Boolean =
150             if (Build.VERSION.SDK_INT >= 24) {
151                 InstrumentationRegistry.getInstrumentation().isSustainedPerformanceModeSupported()
152             } else {
153                 false
154             }
155 
156         private val activityLifecycleCallbacks =
157             object : Application.ActivityLifecycleCallbacks {
onActivityCreatednull158                 override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
159                     if (sustainedPerformanceModeInUse && Build.VERSION.SDK_INT >= 24) {
160                         activity.setSustainedPerformanceMode(true)
161                     }
162 
163                     // Forcibly wake the device, and keep the screen on to prevent benchmark
164                     // failures.
165                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
166                         activity.requestDismissKeyguard()
167                         activity.setShowWhenLocked()
168                         activity.setTurnScreenOn()
169                         activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
170                     } else {
171                         @Suppress("DEPRECATION")
172                         activity.window.addFlags(
173                             WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
174                                 WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
175                                 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
176                                 WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
177                         )
178                     }
179                 }
180 
onActivityDestroyednull181                 override fun onActivityDestroyed(activity: Activity) {}
182 
onActivitySaveInstanceStatenull183                 override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
184 
onActivityStartednull185                 override fun onActivityStarted(activity: Activity) {}
186 
onActivityStoppednull187                 override fun onActivityStopped(activity: Activity) {}
188 
onActivityPausednull189                 override fun onActivityPaused(activity: Activity) {}
190 
onActivityResumednull191                 override fun onActivityResumed(activity: Activity) {}
192             }
193     }
194 }
195