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 val systemUiProcessObserver = SystemUiProcessObserver()
63 
64     @Before
setUpnull65     fun setUp() {
66         assumeTrue(isTelevision)
67 
68         systemUiProcessObserver.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("SystemUI has died during test execution", systemUiProcessObserver.hasDied)
85     }
86 
onSetUpnull87     abstract fun onSetUp()
88 
89     abstract fun onTearDown()
90 
91     protected fun launchActivity(
92         activity: ComponentName? = null,
93         action: String? = null,
94         flags: Set<Int> = setOf(),
95         boolExtras: Map<String, Boolean> = mapOf(),
96         intExtras: Map<String, Int> = mapOf(),
97         stringExtras: Map<String, String> = mapOf()
98     ) {
99         require(activity != null || !action.isNullOrBlank()) {
100             "Cannot launch an activity with neither activity name nor action!"
101         }
102         val command = composeAmShellCommand(
103                 "start", activity, action, flags, boolExtras, intExtras, stringExtras)
104         executeShellCommand(command)
105     }
106 
startForegroundServicenull107     protected fun startForegroundService(
108         service: ComponentName,
109         action: String? = null
110     ) {
111         val command = composeAmShellCommand("start-foreground-service", service, action)
112         executeShellCommand(command)
113     }
114 
sendBroadcastnull115     protected fun sendBroadcast(
116         action: String,
117         flags: Set<Int> = setOf(),
118         boolExtras: Map<String, Boolean> = mapOf(),
119         intExtras: Map<String, Int> = mapOf(),
120         stringExtras: Map<String, String> = mapOf()
121     ) {
122         val command = composeAmShellCommand(
123                 "broadcast", null, action, flags, boolExtras, intExtras, stringExtras)
124         executeShellCommand(command)
125     }
126 
stopPackagenull127     protected fun stopPackage(packageName: String) {
128         SystemUtil.runWithShellPermissionIdentity({
129             activityManager.forceStopPackage(packageName)
130         }, FORCE_STOP_PACKAGES)
131     }
132 
composeAmShellCommandnull133     private fun composeAmShellCommand(
134         command: String,
135         component: ComponentName?,
136         action: String? = null,
137         flags: Set<Int> = setOf(),
138         boolExtras: Map<String, Boolean> = mapOf(),
139         intExtras: Map<String, Int> = mapOf(),
140         stringExtras: Map<String, String> = mapOf()
141     ): String = buildString {
142         append("am ")
143         append(command)
144         component?.let {
145             append(" -n ")
146             append(it.flattenToShortString())
147         }
148         action?.let {
149             append(" -a ")
150             append(it)
151         }
152         flags.forEach {
153             append(" -f ")
154             append(it)
155         }
156         boolExtras.forEach {
157             append(it.withFlag("ez"))
158         }
159         intExtras.forEach {
160             append(it.withFlag("ei"))
161         }
162         stringExtras.forEach {
163             append(it.withFlag("es"))
164         }
165     }
166 
withFlagnull167     private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"
168 
169     protected fun executeShellCommand(cmd: String): String {
170         try {
171             return SystemUtil.runShellCommand(instrumentation, cmd)
172         } catch (e: IOException) {
173             Log.e(TAG, "Error running shell command: $cmd")
174             throw e
175         }
176     }
177 
178     inner class SystemUiProcessObserver : IProcessObserver.Stub() {
179         private val activityManager: IActivityManager = ActivityManager.getService()
180         private val uiAutomation = instrumentation.uiAutomation
181         private val systemUiUid = packageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0)
182         var hasDied: Boolean = false
183 
startnull184         fun start() {
185             hasDied = false
186             uiAutomation.adoptShellPermissionIdentity(
187                     android.Manifest.permission.SET_ACTIVITY_WATCHER)
188             activityManager.registerProcessObserver(this)
189         }
190 
stopnull191         fun stop() {
192             activityManager.unregisterProcessObserver(this)
193             uiAutomation.dropShellPermissionIdentity()
194         }
195 
onForegroundActivitiesChangednull196         override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
197 
onForegroundServicesChangednull198         override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
199 
onProcessDiednull200         override fun onProcessDied(pid: Int, uid: Int) {
201             if (uid == systemUiUid) hasDied = true
202         }
203     }
204 }