blob: 513e1dfebf4fa4057d520f30344e209439533a6a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.permission.cts
import android.accessibility.cts.common.InstrumentedAccessibilityService
import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule
import android.app.Instrumentation
import android.app.UiAutomation
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Process
import android.permission.cts.NotificationListenerUtils.assertEmptyNotification
import android.permission.cts.NotificationListenerUtils.assertNotificationExist
import android.permission.cts.NotificationListenerUtils.cancelNotification
import android.permission.cts.NotificationListenerUtils.cancelNotifications
import android.permission.cts.NotificationListenerUtils.getNotification
import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueDoesNotExist
import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueExist
import android.permission.cts.SafetyCenterUtils.assertSafetyCenterStarted
import android.permission.cts.SafetyCenterUtils.deleteDeviceConfigPrivacyProperty
import android.permission.cts.SafetyCenterUtils.deviceSupportsSafetyCenter
import android.permission.cts.SafetyCenterUtils.setDeviceConfigPrivacyProperty
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
import android.safetycenter.SafetyCenterManager
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.compatibility.common.util.DeviceConfigStateChangerRule
import com.android.compatibility.common.util.ProtoUtils
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.server.job.nano.JobSchedulerServiceDumpProto
import org.junit.After
import org.junit.Assert
import org.junit.Assume
import org.junit.Before
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@AppModeFull(
reason = "Cannot set system settings as instant app. Also we never show an accessibility " +
"notification for instant apps."
)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
class AccessibilityPrivacySourceTest {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val context: Context = instrumentation.targetContext
private val mAccessibilityServiceRule =
InstrumentedAccessibilityServiceTestRule(AccessibilityTestService::class.java, false)
private val permissionControllerPackage = context.packageManager.permissionControllerPackageName
private val accessibilityTestService =
ComponentName(context, AccessibilityTestService::class.java).flattenToString()
private val safetyCenterIssueId = "accessibility_$accessibilityTestService"
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)
@get:Rule
val deviceConfigSafetyCenterEnabled =
DeviceConfigStateChangerRule(
context, DeviceConfig.NAMESPACE_PRIVACY, SAFETY_CENTER_ENABLED, true.toString())
@get:Rule
val deviceConfigA11ySourceEnabled =
DeviceConfigStateChangerRule(
context, DeviceConfig.NAMESPACE_PRIVACY, ACCESSIBILITY_SOURCE_ENABLED, true.toString())
@get:Rule
val deviceConfigA11yListenerDisabled =
DeviceConfigStateChangerRule(
context,
DeviceConfig.NAMESPACE_PRIVACY,
ACCESSIBILITY_LISTENER_ENABLED,
false.toString())
@Before
fun setup() {
Assume.assumeTrue(deviceSupportsSafetyCenter(context))
InstrumentedAccessibilityService.disableAllServices()
runShellCommand("input keyevent KEYCODE_WAKEUP")
resetPermissionController()
cancelNotifications(permissionControllerPackage)
}
@After
fun cleanup() {
cancelNotifications(permissionControllerPackage)
runWithShellPermissionIdentity { safetyCenterManager?.clearAllSafetySourceDataForTests() }
}
@Test
fun testJobSendsNotification() {
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
}
@Test
fun testJobSendsNotificationOnEnable() {
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
InstrumentedAccessibilityService.disableAllServices()
setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, false.toString())
setDeviceConfigPrivacyProperty(ACCESSIBILITY_JOB_INTERVAL_MILLIS, "0")
// enable service again and verify a notification
try {
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
} finally {
deleteDeviceConfigPrivacyProperty(ACCESSIBILITY_JOB_INTERVAL_MILLIS)
}
}
@Test
fun testJobSendsIssuesToSafetyCenter() {
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertSafetyCenterIssueExist(
SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
}
@Test
fun testJobDoesNotSendNotificationInSecondRunForSameService() {
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
runJobAndWaitUntilCompleted()
assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
}
@Test
fun testAccessibilityListenerSendsIssueToSafetyCenter() {
setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
val automation = getAutomation()
mAccessibilityServiceRule.enableService()
TestUtils.eventually(
{
assertSafetyCenterIssueExist(
SC_ACCESSIBILITY_SOURCE_ID,
safetyCenterIssueId,
SC_ACCESSIBILITY_ISSUE_TYPE_ID,
automation)
},
TIMEOUT_MILLIS)
automation.destroy()
}
@Test
fun testJobWithDisabledServiceDoesNotSendNotification() {
runJobAndWaitUntilCompleted()
assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
}
@Test
fun testJobWithDisabledServiceDoesNotSendIssueToSafetyCenter() {
runJobAndWaitUntilCompleted()
assertSafetyCenterIssueDoesNotExist(
SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
}
@Test
fun testJobWithAccessibilityFeatureDisabledDoesNotSendNotification() {
setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
}
@Test
fun testJobWithAccessibilityFeatureDisabledDoesNotSendIssueToSafetyCenter() {
setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertSafetyCenterIssueDoesNotExist(
SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
}
@Test
fun testJobWithSafetyCenterDisabledDoesNotSendNotification() {
setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
}
@Test
fun testJobWithSafetyCenterDisabledDoesNotSendIssueToSafetyCenter() {
setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
assertSafetyCenterIssueDoesNotExist(
SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
}
@Test
fun testNotificationClickOpenSafetyCenter() {
mAccessibilityServiceRule.enableService()
runJobAndWaitUntilCompleted()
val statusBarNotification =
getNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
Assert.assertNotNull(statusBarNotification)
val contentIntent = statusBarNotification!!.notification.contentIntent
contentIntent.send()
assertSafetyCenterStarted()
}
private fun getAutomation(): UiAutomation {
return instrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES)
}
private fun runJobAndWaitUntilCompleted() {
TestUtils.runJobAndWaitUntilCompleted(
permissionControllerPackage, ACCESSIBILITY_JOB_ID, TIMEOUT_MILLIS, getAutomation())
}
/** Reset the permission controllers state. */
@Throws(Throwable::class)
private fun resetPermissionController() {
PermissionUtils.clearAppState(permissionControllerPackage)
val currentUserId = Process.myUserHandle().identifier
// Wait until jobs are cleared
TestUtils.eventually(
{
val dump = getJobSchedulerDump()
for (job in dump!!.registeredJobs) {
if (job.dump.sourceUserId == currentUserId &&
job.dump.sourcePackageName == permissionControllerPackage) {
Assert.assertFalse(
job.dump.jobInfo.service.className.contains("AccessibilityJobService"))
}
}
},
TIMEOUT_MILLIS)
runShellCommand(
"cmd jobscheduler reset-execution-quota -u " +
"${Process.myUserHandle().identifier} $permissionControllerPackage")
// Setup up permission controller again (simulate a reboot)
val permissionControllerSetupIntent =
Intent(ACTION_SET_UP_ACCESSIBILITY_CHECK).apply {
setPackage(permissionControllerPackage)
setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
}
// Query for the setup broadcast receiver
val resolveInfos =
context.packageManager.queryBroadcastReceivers(permissionControllerSetupIntent, 0)
if (resolveInfos.size > 0) {
context.sendBroadcast(permissionControllerSetupIntent)
} else {
context.sendBroadcast(
Intent().apply {
setClassName(permissionControllerPackage, AccessibilityOnBootReceiver)
setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
setPackage(permissionControllerPackage)
})
}
// Wait until jobs are set up
TestUtils.eventually(
{
val dump = getJobSchedulerDump()
for (job in dump!!.registeredJobs) {
if (job.dump.sourceUserId == currentUserId &&
job.dump.sourcePackageName == permissionControllerPackage &&
job.dump.jobInfo.service.className.contains("AccessibilityJobService")) {
return@eventually
}
}
Assert.fail("accessibility job not found")
},
TIMEOUT_MILLIS)
}
@Throws(Exception::class)
private fun getJobSchedulerDump(): JobSchedulerServiceDumpProto? {
return ProtoUtils.getProto(
getAutomation(),
JobSchedulerServiceDumpProto::class.java,
ProtoUtils.DUMPSYS_JOB_SCHEDULER)
}
companion object {
private const val SC_ACCESSIBILITY_SOURCE_ID = "AndroidAccessibility"
private const val ACCESSIBILITY_SOURCE_ENABLED = "sc_accessibility_source_enabled"
private const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
private const val ACCESSIBILITY_LISTENER_ENABLED = "sc_accessibility_listener_enabled"
private const val ACCESSIBILITY_JOB_INTERVAL_MILLIS = "sc_accessibility_job_interval_millis"
private const val ACCESSIBILITY_JOB_ID = 6
private const val ACCESSIBILITY_NOTIFICATION_ID = 4
private const val TIMEOUT_MILLIS: Long = 10000
private const val SC_ACCESSIBILITY_ISSUE_TYPE_ID = "accessibility_privacy_issue"
private const val AccessibilityOnBootReceiver =
"com.android.permissioncontroller.privacysources.AccessibilityOnBootReceiver"
private const val ACTION_SET_UP_ACCESSIBILITY_CHECK =
"com.android.permissioncontroller.action.SET_UP_ACCESSIBILITY_CHECK"
@get:ClassRule
@JvmStatic
val ctsNotificationListenerHelper =
CtsNotificationListenerHelperRule(
InstrumentationRegistry.getInstrumentation().targetContext)
}
}