1 /* 2 * Copyright (C) 2020 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 android.systemui.cts.tv 18 19 import android.Manifest.permission.FORCE_STOP_PACKAGES 20 import android.app.ActivityManager 21 import android.app.IActivityManager 22 import android.app.IProcessObserver 23 import android.app.Instrumentation 24 import android.content.ComponentName 25 import android.content.Context 26 import android.content.pm.PackageManager 27 import android.content.pm.PackageManager.FEATURE_LEANBACK 28 import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY 29 import android.os.SystemClock 30 import android.server.wm.UiDeviceUtils 31 import android.server.wm.WindowManagerStateHelper 32 import android.systemui.tv.cts.ResourceNames.SYSTEM_UI_PACKAGE 33 import android.util.Log 34 import androidx.test.platform.app.InstrumentationRegistry 35 import androidx.test.uiautomator.UiDevice 36 import com.android.compatibility.common.util.SystemUtil 37 import org.junit.After 38 import org.junit.Assert.assertFalse 39 import org.junit.Assume.assumeTrue 40 import org.junit.Before 41 import java.io.IOException 42 43 abstract class TvTestBase { 44 companion object { 45 private const val TAG = "TvTestBase" 46 private const val AFTER_TEST_PROCESS_CHECK_DELAY = 1_000L // 1 sec 47 } 48 49 protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() 50 protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) 51 protected val context: Context = instrumentation.context 52 protected val packageManager: PackageManager = context.packageManager 53 ?: error("Could not get a PackageManager") 54 protected val activityManager: ActivityManager = 55 context.getSystemService(ActivityManager::class.java) 56 ?: error("Could not get a ActivityManager") 57 protected val wmState: WindowManagerStateHelper = WindowManagerStateHelper() 58 private val isTelevision: Boolean <lambda>null59 get() = packageManager.run { 60 hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) 61 } 62 private var systemUiProcessObserver : SystemUiProcessObserver? = null 63 64 @Before setUpnull65 fun setUp() { 66 assumeTrue(isTelevision) 67 68 systemUiProcessObserver = SystemUiProcessObserver().apply { start() } 69 70 UiDeviceUtils.pressWakeupButton() 71 UiDeviceUtils.pressUnlockButton() 72 73 onSetUp() 74 } 75 76 @After tearDownnull77 fun tearDown() { 78 if (!isTelevision) return 79 80 onTearDown() 81 82 SystemClock.sleep(AFTER_TEST_PROCESS_CHECK_DELAY) 83 systemUiProcessObserver?.stop() 84 assertFalse( 85 "SystemUI has died during test execution", systemUiProcessObserver?.hasDied ?: true) 86 } 87 onSetUpnull88 abstract fun onSetUp() 89 90 abstract fun onTearDown() 91 92 protected fun launchActivity( 93 activity: ComponentName? = null, 94 action: String? = null, 95 flags: Set<Int> = setOf(), 96 boolExtras: Map<String, Boolean> = mapOf(), 97 intExtras: Map<String, Int> = mapOf(), 98 stringExtras: Map<String, String> = mapOf() 99 ) { 100 require(activity != null || !action.isNullOrBlank()) { 101 "Cannot launch an activity with neither activity name nor action!" 102 } 103 val command = composeAmShellCommand( 104 "start", activity, action, flags, boolExtras, intExtras, stringExtras) 105 executeShellCommand(command) 106 } 107 startForegroundServicenull108 protected fun startForegroundService( 109 service: ComponentName, 110 action: String? = null 111 ) { 112 val command = composeAmShellCommand("start-foreground-service", service, action) 113 executeShellCommand(command) 114 } 115 sendBroadcastnull116 protected fun sendBroadcast( 117 action: String, 118 flags: Set<Int> = setOf(), 119 boolExtras: Map<String, Boolean> = mapOf(), 120 intExtras: Map<String, Int> = mapOf(), 121 stringExtras: Map<String, String> = mapOf() 122 ) { 123 val command = composeAmShellCommand( 124 "broadcast", null, action, flags, boolExtras, intExtras, stringExtras) 125 executeShellCommand(command) 126 } 127 stopPackagenull128 protected fun stopPackage(packageName: String) { 129 SystemUtil.runWithShellPermissionIdentity({ 130 activityManager.forceStopPackage(packageName) 131 }, FORCE_STOP_PACKAGES) 132 } 133 composeAmShellCommandnull134 private fun composeAmShellCommand( 135 command: String, 136 component: ComponentName?, 137 action: String? = null, 138 flags: Set<Int> = setOf(), 139 boolExtras: Map<String, Boolean> = mapOf(), 140 intExtras: Map<String, Int> = mapOf(), 141 stringExtras: Map<String, String> = mapOf() 142 ): String = buildString { 143 append("am ") 144 append(command) 145 component?.let { 146 append(" -n ") 147 append(it.flattenToShortString()) 148 } 149 action?.let { 150 append(" -a ") 151 append(it) 152 } 153 flags.forEach { 154 append(" -f ") 155 append(it) 156 } 157 boolExtras.forEach { 158 append(it.withFlag("ez")) 159 } 160 intExtras.forEach { 161 append(it.withFlag("ei")) 162 } 163 stringExtras.forEach { 164 append(it.withFlag("es")) 165 } 166 } 167 withFlagnull168 private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value" 169 170 protected fun executeShellCommand(cmd: String): String { 171 try { 172 return SystemUtil.runShellCommand(instrumentation, cmd) 173 } catch (e: IOException) { 174 Log.e(TAG, "Error running shell command: $cmd") 175 throw e 176 } 177 } 178 179 inner class SystemUiProcessObserver : IProcessObserver.Stub() { 180 private val activityManager: IActivityManager = ActivityManager.getService() 181 private val uiAutomation = instrumentation.uiAutomation 182 private val systemUiUid = packageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0) 183 var hasDied: Boolean = false 184 startnull185 fun start() { 186 hasDied = false 187 uiAutomation.adoptShellPermissionIdentity( 188 android.Manifest.permission.SET_ACTIVITY_WATCHER) 189 activityManager.registerProcessObserver(this) 190 } 191 stopnull192 fun stop() { 193 activityManager.unregisterProcessObserver(this) 194 uiAutomation.dropShellPermissionIdentity() 195 } 196 onProcessStartednull197 override fun onProcessStarted( 198 pid: Int, 199 processUid: Int, 200 packageUid: Int, 201 packageName: String, 202 processName: String 203 ) {} 204 onForegroundActivitiesChangednull205 override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {} 206 onForegroundServicesChangednull207 override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {} 208 onProcessDiednull209 override fun onProcessDied(pid: Int, uid: Int) { 210 if (uid == systemUiUid) hasDied = true 211 } 212 } 213 } 214