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