• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 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.os.cts
18 
19 import android.app.Activity
20 import android.app.ActivityManager
21 import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
22 import android.app.Instrumentation
23 import android.app.UiAutomation
24 import android.content.BroadcastReceiver
25 import android.content.Context
26 import android.content.Intent
27 import android.content.pm.PackageManager
28 import android.os.Handler
29 import android.os.Looper
30 import android.os.ParcelFileDescriptor
31 import android.os.Process
32 import android.provider.DeviceConfig
33 import android.support.test.uiautomator.By
34 import android.support.test.uiautomator.BySelector
35 import android.support.test.uiautomator.UiDevice
36 import android.support.test.uiautomator.UiObject2
37 import android.support.test.uiautomator.UiScrollable
38 import android.support.test.uiautomator.UiSelector
39 import android.support.test.uiautomator.Until
40 import android.util.Log
41 import androidx.test.InstrumentationRegistry
42 import com.android.compatibility.common.util.ExceptionUtils.wrappingExceptions
43 import com.android.compatibility.common.util.FeatureUtil
44 import com.android.compatibility.common.util.LogcatInspector
45 import com.android.compatibility.common.util.SystemUtil.eventually
46 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
47 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
48 import com.android.compatibility.common.util.ThrowingSupplier
49 import com.android.compatibility.common.util.UiAutomatorUtils
50 import com.android.compatibility.common.util.UiDumpUtils
51 import com.android.compatibility.common.util.click
52 import com.android.compatibility.common.util.depthFirstSearch
53 import com.android.compatibility.common.util.textAsString
54 import org.hamcrest.Matcher
55 import org.hamcrest.Matchers
56 import org.junit.Assert
57 import org.junit.Assert.assertThat
58 import org.junit.Assert.assertTrue
59 import java.io.InputStream
60 import java.util.concurrent.CountDownLatch
61 import java.util.concurrent.TimeUnit
62 
63 private const val BROADCAST_TIMEOUT_MS = 60000L
64 
65 const val HIBERNATION_BOOT_RECEIVER_CLASS_NAME =
66     "com.android.permissioncontroller.hibernation.HibernationOnBootReceiver"
67 const val ACTION_SET_UP_HIBERNATION =
68     "com.android.permissioncontroller.action.SET_UP_HIBERNATION"
69 
70 const val SYSUI_PKG_NAME = "com.android.systemui"
71 const val NOTIF_LIST_ID = "com.android.systemui:id/notification_stack_scroller"
72 const val NOTIF_LIST_ID_AUTOMOTIVE = "com.android.systemui:id/notifications"
73 const val CLEAR_ALL_BUTTON_ID = "dismiss_text"
74 // Time to find a notification. Unlikely, but in cases with a lot of notifications, it may take
75 // time to find the notification we're looking for
76 const val NOTIF_FIND_TIMEOUT = 20000L
77 const val VIEW_WAIT_TIMEOUT = 3000L
78 
79 const val CMD_EXPAND_NOTIFICATIONS = "cmd statusbar expand-notifications"
80 const val CMD_COLLAPSE = "cmd statusbar collapse"
81 
82 const val APK_PATH_S_APP = "/data/local/tmp/cts/os/CtsAutoRevokeSApp.apk"
83 const val APK_PACKAGE_NAME_S_APP = "android.os.cts.autorevokesapp"
84 const val APK_PATH_R_APP = "/data/local/tmp/cts/os/CtsAutoRevokeRApp.apk"
85 const val APK_PACKAGE_NAME_R_APP = "android.os.cts.autorevokerapp"
86 const val APK_PATH_Q_APP = "/data/local/tmp/cts/os/CtsAutoRevokeQApp.apk"
87 const val APK_PACKAGE_NAME_Q_APP = "android.os.cts.autorevokeqapp"
88 
89 fun runBootCompleteReceiver(context: Context, testTag: String) {
90     val pkgManager = context.packageManager
91     val permissionControllerPkg = pkgManager.permissionControllerPackageName
92     var permissionControllerSetupIntent = Intent(ACTION_SET_UP_HIBERNATION).apply {
93         setPackage(permissionControllerPkg)
94         setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
95     }
96     val receivers = pkgManager.queryBroadcastReceivers(
97         permissionControllerSetupIntent, /* flags= */ 0)
98     if (receivers.size == 0) {
99         // May be on an older, pre-built PermissionController. In this case, try sending directly.
100         permissionControllerSetupIntent = Intent().apply {
101             setPackage(permissionControllerPkg)
102             setClassName(permissionControllerPkg, HIBERNATION_BOOT_RECEIVER_CLASS_NAME)
103             setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
104         }
105     }
106     val countdownLatch = CountDownLatch(1)
107     Log.d(testTag, "Sending boot complete broadcast directly to $permissionControllerPkg")
108     context.sendOrderedBroadcast(
109         permissionControllerSetupIntent,
110         /* receiverPermission= */ null,
111         object : BroadcastReceiver() {
112             override fun onReceive(context: Context?, intent: Intent?) {
113                 countdownLatch.countDown()
114                 Log.d(testTag, "Broadcast received by $permissionControllerPkg")
115             }
116         },
117         Handler.createAsync(Looper.getMainLooper()),
118         Activity.RESULT_OK,
119         /* initialData= */ null,
120         /* initialExtras= */ null)
121     assertTrue("Timed out while waiting for boot receiver broadcast to be received",
122         countdownLatch.await(BROADCAST_TIMEOUT_MS, TimeUnit.MILLISECONDS))
123 }
124 
runAppHibernationJobnull125 fun runAppHibernationJob(context: Context, tag: String) {
126     val logcat = Logcat()
127 
128     // Sometimes first run observes stale package data
129     // so run twice to prevent that
130     repeat(2) {
131         val mark = logcat.mark(tag)
132         eventually {
133             runShellCommandOrThrow("cmd jobscheduler run -u " +
134                 "${Process.myUserHandle().identifier} -f " +
135                 "${context.packageManager.permissionControllerPackageName} 2")
136         }
137         logcat.assertLogcatContainsInOrder("*:*", 30_000,
138             mark,
139             "onStartJob",
140             "Done auto-revoke for user")
141     }
142 }
143 
withAppnull144 inline fun withApp(
145     apk: String,
146     packageName: String,
147     action: () -> Unit
148 ) {
149     installApk(apk)
150     try {
151         // Try to reduce flakiness caused by new package update not propagating in time
152         Thread.sleep(1000)
153         action()
154     } finally {
155         uninstallApp(packageName)
156     }
157 }
158 
withAppNoUninstallAssertionnull159 inline fun withAppNoUninstallAssertion(
160     apk: String,
161     packageName: String,
162     action: () -> Unit
163 ) {
164     installApk(apk)
165     try {
166         // Try to reduce flakiness caused by new package update not propagating in time
167         Thread.sleep(1000)
168         action()
169     } finally {
170         uninstallAppWithoutAssertion(packageName)
171     }
172 }
173 
withDeviceConfignull174 inline fun <T> withDeviceConfig(
175     namespace: String,
176     name: String,
177     value: String,
178     action: () -> T
179 ): T {
180     val oldValue = runWithShellPermissionIdentity(ThrowingSupplier {
181         DeviceConfig.getProperty(namespace, name)
182     })
183     try {
184         runWithShellPermissionIdentity {
185             DeviceConfig.setProperty(namespace, name, value, false /* makeDefault */)
186         }
187         return action()
188     } finally {
189         runWithShellPermissionIdentity {
190             DeviceConfig.setProperty(namespace, name, oldValue, false /* makeDefault */)
191         }
192     }
193 }
194 
withUnusedThresholdMsnull195 inline fun <T> withUnusedThresholdMs(threshold: Long, action: () -> T): T {
196     return withDeviceConfig(
197         DeviceConfig.NAMESPACE_PERMISSIONS, "auto_revoke_unused_threshold_millis2",
198         threshold.toString(), action)
199 }
200 
awaitAppStatenull201 fun awaitAppState(pkg: String, stateMatcher: Matcher<Int>) {
202     val context: Context = InstrumentationRegistry.getTargetContext()
203     eventually {
204         runWithShellPermissionIdentity {
205             val packageImportance = context
206                 .getSystemService(ActivityManager::class.java)!!
207                 .getPackageImportance(pkg)
208             assertThat(packageImportance, stateMatcher)
209         }
210     }
211 }
212 
startAppnull213 fun startApp(packageName: String) {
214     val context = InstrumentationRegistry.getTargetContext()
215     val intent = context.packageManager.getLaunchIntentForPackage(packageName)
216     context.startActivity(intent)
217     awaitAppState(packageName, Matchers.lessThanOrEqualTo(IMPORTANCE_TOP_SLEEPING))
218     waitForIdle()
219 }
220 
goHomenull221 fun goHome() {
222     runShellCommandOrThrow("input keyevent KEYCODE_HOME")
223 }
224 
225 /**
226  * Open the "unused apps" notification which is sent after the hibernation job.
227  */
openUnusedAppsNotificationnull228 fun openUnusedAppsNotification() {
229     val notifSelector = By.textContains("unused app")
230     if (hasFeatureWatch()) {
231         val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
232         expandNotificationsWatch(UiAutomatorUtils.getUiDevice())
233         waitFindObject(uiAutomation, notifSelector).click()
234         // In wear os, notification has one additional button to open it
235         waitFindObject(uiAutomation, By.text("Open")).click()
236     } else {
237         runShellCommandOrThrow(CMD_EXPAND_NOTIFICATIONS)
238         waitFindNotification(notifSelector, NOTIF_FIND_TIMEOUT).click()
239     }
240 }
241 
hasFeatureWatchnull242 fun hasFeatureWatch(): Boolean {
243     return InstrumentationRegistry.getTargetContext().packageManager.hasSystemFeature(
244         PackageManager.FEATURE_WATCH)
245 }
246 
hasFeatureTVnull247 fun hasFeatureTV(): Boolean {
248     return InstrumentationRegistry.getTargetContext().packageManager.hasSystemFeature(
249             PackageManager.FEATURE_LEANBACK) ||
250             InstrumentationRegistry.getTargetContext().packageManager.hasSystemFeature(
251                     PackageManager.FEATURE_TELEVISION)
252 }
253 
expandNotificationsWatchnull254 private fun expandNotificationsWatch(uiDevice: UiDevice) {
255     with(uiDevice) {
256         wakeUp()
257         // Swipe up from bottom to reveal notifications
258         val x = displayWidth / 2
259         swipe(x, displayHeight, x, 0, 1)
260     }
261 }
262 
263 /**
264  * Reset to the top of the notifications list.
265  */
resetNotificationsnull266 private fun resetNotifications(notificationList: UiScrollable) {
267     runShellCommandOrThrow(CMD_COLLAPSE)
268     notificationList.waitUntilGone(VIEW_WAIT_TIMEOUT)
269     runShellCommandOrThrow(CMD_EXPAND_NOTIFICATIONS)
270 }
271 
waitFindNotificationnull272 private fun waitFindNotification(selector: BySelector, timeoutMs: Long):
273     UiObject2 {
274     var view: UiObject2? = null
275     val start = System.currentTimeMillis()
276     val uiDevice = UiAutomatorUtils.getUiDevice()
277 
278     var isAtEnd = false
279     var wasScrolledUpAlready = false
280     while (view == null && start + timeoutMs > System.currentTimeMillis()) {
281         view = uiDevice.wait(Until.findObject(selector), VIEW_WAIT_TIMEOUT)
282         if (view == null) {
283             val notificationListId = if (FeatureUtil.isAutomotive()) {
284                 NOTIF_LIST_ID_AUTOMOTIVE
285             } else {
286                 NOTIF_LIST_ID
287             }
288             val notificationList = UiScrollable(UiSelector().resourceId(notificationListId))
289             wrappingExceptions({ cause: Throwable? -> UiDumpUtils.wrapWithUiDump(cause) }) {
290                 Assert.assertTrue("Notification list view not found",
291                     notificationList.waitForExists(VIEW_WAIT_TIMEOUT))
292             }
293             if (isAtEnd) {
294                 if (wasScrolledUpAlready) {
295                     break
296                 }
297                 resetNotifications(notificationList)
298                 isAtEnd = false
299                 wasScrolledUpAlready = true
300             } else {
301                 notificationList.scrollForward()
302                 isAtEnd = uiDevice.hasObject(By.res(SYSUI_PKG_NAME, CLEAR_ALL_BUTTON_ID))
303             }
304         }
305     }
306     wrappingExceptions({ cause: Throwable? -> UiDumpUtils.wrapWithUiDump(cause) }) {
307         Assert.assertNotNull("View not found after waiting for " + timeoutMs + "ms: " + selector,
308             view)
309     }
310     return view!!
311 }
312 
waitFindObjectnull313 fun waitFindObject(uiAutomation: UiAutomation, selector: BySelector): UiObject2 {
314     try {
315         return UiAutomatorUtils.waitFindObject(selector)
316     } catch (e: RuntimeException) {
317         val ui = uiAutomation.rootInActiveWindow
318 
319         val title = ui.depthFirstSearch { node ->
320             node.viewIdResourceName?.contains("alertTitle") == true
321         }
322         val okCloseButton = ui.depthFirstSearch { node ->
323             (node.textAsString?.equals("OK", ignoreCase = true) ?: false)  ||
324                 (node.textAsString?.equals("Close app", ignoreCase = true) ?: false)
325         }
326         val titleString = title?.text?.toString()
327         if (okCloseButton != null &&
328             titleString != null &&
329             (titleString == "Android System" ||
330                 titleString.endsWith("keeps stopping"))) {
331             // Auto dismiss occasional system dialogs to prevent interfering with the test
332             android.util.Log.w(AutoRevokeTest.LOG_TAG, "Ignoring exception", e)
333             okCloseButton.click()
334             return UiAutomatorUtils.waitFindObject(selector)
335         } else {
336             throw e
337         }
338     }
339 }
340 
341 class Logcat() : LogcatInspector() {
executeShellCommandnull342     override fun executeShellCommand(command: String?): InputStream {
343         val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
344         return ParcelFileDescriptor.AutoCloseInputStream(
345             instrumentation.uiAutomation.executeShellCommand(command))
346     }
347 }
348