blob: 11968d36487bafc7aad588b5552906dd46ad3921 [file] [log] [blame]
/*
* Copyright (C) 2021 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.permission3.cts
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.RECORD_AUDIO
import android.app.ActivityManager
import android.app.ActivityOptions
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.UserHandle
import android.provider.Settings
import android.support.test.uiautomator.By
import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import org.junit.After
import org.junit.Assert
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import java.util.concurrent.CountDownLatch
const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
const val EXTRA_CREATE_CHANNELS = "extra_create"
const val EXTRA_CREATE_CHANNELS_DELAYED = "extra_create_delayed"
const val EXTRA_REQUEST_OTHER_PERMISSIONS = "extra_request_permissions"
const val EXTRA_REQUEST_NOTIF_PERMISSION = "extra_request_notif_permission"
const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
const val EXTRA_START_SECOND_ACTIVITY = "extra_start_second_activity"
const val EXTRA_START_SECOND_APP = "extra_start_second_app"
const val ACTIVITY_NAME = "CreateNotificationChannelsActivity"
const val ACTIVITY_LABEL = "CreateNotif"
const val SECOND_ACTIVITY_LABEL = "EmptyActivity"
const val ALLOW = "to send you"
const val INTENT_ACTION = "usepermission.createchannels.MAIN"
const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
const val NOTIFICATION_PERMISSION_ENABLED = "notification_permission_enabled"
const val EXPECTED_TIMEOUT_MS = 2000L
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
class NotificationPermissionTest : BaseUsePermissionTest() {
private val cr = callWithShellPermissionIdentity {
context.createContextAsUser(UserHandle.SYSTEM, 0).contentResolver
}
private var previousEnableState = -1
private var countDown: CountDownLatch = CountDownLatch(1)
private var allowedGroups = listOf<String>()
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
allowedGroups = intent?.getStringArrayListExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) ?: emptyList()
countDown.countDown()
}
}
@Before
fun setLatchAndEnablePermission() {
// b/220968160: Notification permission is not enabled on TV devices.
assumeFalse(isTv)
runWithShellPermissionIdentity {
previousEnableState = Settings.Secure.getInt(cr, NOTIFICATION_PERMISSION_ENABLED, 0)
Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, 1)
}
countDown = CountDownLatch(1)
allowedGroups = listOf()
context.registerReceiver(receiver, IntentFilter(BROADCAST_ACTION))
}
@After
fun resetPermissionAndRemoveReceiver() {
if (previousEnableState >= 0) {
runWithShellPermissionIdentity {
Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
}
context.unregisterReceiver(receiver)
}
}
@Test
fun notificationPermissionAddedForLegacyApp() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
runWithShellPermissionIdentity {
Assert.assertTrue("SDK < 32 apps should have POST_NOTIFICATIONS added implicitly",
context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
PackageManager.GET_PERMISSIONS).requestedPermissions
.contains(POST_NOTIFICATIONS))
}
}
@Test
fun notificationPermissionIsNotImplicitlyAddedTo33Apps() {
installPackage(APP_APK_PATH_LATEST_NONE, expectSuccess = true)
runWithShellPermissionIdentity {
val requestedPerms = context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
PackageManager.GET_PERMISSIONS).requestedPermissions
Assert.assertTrue("SDK >= 33 apps should NOT have POST_NOTIFICATIONS added implicitly",
requestedPerms == null || !requestedPerms.contains(POST_NOTIFICATIONS))
}
}
@Test
fun notificationPromptShowsForLegacyAppAfterCreatingNotificationChannels() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp()
clickPermissionRequestAllowButton()
}
@Test
fun notificationPromptShowsForLegacyAppWithNotificationChannelsOnStart() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
// create channels, then leave the app
launchApp()
killTestApp()
launchApp()
waitFindObject(By.textContains(ALLOW))
clickPermissionRequestAllowButton()
}
@Test
fun notificationPromptDoesNotShowForLegacyAppWithNoNotificationChannels_onLaunch() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(createChannels = false)
assertDialogNotShowing()
}
@Test
fun notificationPromptDoesNotShowForNonLauncherIntentCategoryLaunches_onChannelCreate() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(launcherCategory = false)
assertDialogNotShowing()
}
@Test
fun notificationPromptDoesNotShowForNonLauncherIntentCategoryLaunches_onLaunch() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
// create channels, then leave the app
launchApp()
killTestApp()
launchApp(launcherCategory = false)
assertDialogNotShowing()
}
@Test
fun notificationPromptDoesNotShowForNonMainIntentActionLaunches_onLaunch() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
// create channels, then leave the app
launchApp()
killTestApp()
launchApp(mainIntent = false)
assertDialogNotShowing()
}
@Test
fun notificationPromptDoesNotShowForNonMainIntentActionLaunches_onChannelCreate() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(mainIntent = false)
assertDialogNotShowing()
}
@Test
fun notificationPromptShowsIfActivityOptionSet() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
// create channels, then leave the app
launchApp()
killTestApp()
launchApp(mainIntent = false, isEligibleForPromptOption = true)
clickPermissionRequestAllowButton()
}
@Test
fun notificationPromptShownForSubsequentStartsIfTaskStartWasLauncher() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(startSecondActivity = true)
pressBack()
clickPermissionRequestAllowButton()
}
@Test
fun notificationPromptNotShownForSubsequentStartsIfTaskStartWasNotLauncher() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(mainIntent = false, startSecondActivity = true)
assertDialogNotShowing()
}
@Test
fun notificationPromptShownForChannelCreateInSecondActivityIfTaskStartWasLauncher() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(startSecondActivity = true, createChannels = false)
clickPermissionRequestAllowButton()
}
@Test
fun notificationPromptNotShownForChannelCreateInSecondActivityIfTaskStartWasntLauncher() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(mainIntent = false, startSecondActivity = true, createChannels = false)
assertDialogNotShowing()
}
@Test
fun notificationPromptNotShownForSubsequentStartsIfSubsequentIsDifferentPkg() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
installPackage(APP_APK_PATH_OTHER_APP, expectSuccess = true)
// perform a launcher start, then start a secondary app
launchApp(startSecondaryAppAndCreateChannelsAfterSecondStart = true)
try {
waitFindObject(By.textContains(SECOND_ACTIVITY_LABEL))
assertDialogNotShowing()
} finally {
uninstallPackage(OTHER_APP_PACKAGE_NAME)
}
}
@Test
fun notificationGrantedOnLegacyGrant() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp()
clickPermissionRequestAllowButton()
assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
}
@Test
fun nonSystemServerPackageCannotShowPromptForOtherPackage() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
runWithShellPermissionIdentity {
val grantPermission = Intent(PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
grantPermission.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES,
arrayOf(POST_NOTIFICATIONS))
grantPermission.setPackage(context.packageManager.permissionControllerPackageName)
grantPermission.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(grantPermission)
}
try {
clickPermissionRequestAllowButton(timeoutMillis = EXPECTED_TIMEOUT_MS)
Assert.fail("Expected not to find permission request dialog")
} catch (expected: RuntimeException) {
// Do nothing
}
}
@Test
fun mergeAppPermissionRequestIntoNotificationAndVerifyResult() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(requestPermissionsDelayed = true)
clickPermissionRequestAllowButton()
assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
clickPermissionRequestAllowForegroundButton()
assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
countDown.await()
// Result should contain only the microphone request
Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
}
@Test
fun mergeNotificationRequestIntoAppPermissionRequestAndVerifyResult() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(createChannels = false, createChannelsDelayed = true, requestPermissions = true)
clickPermissionRequestAllowForegroundButton()
assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
clickPermissionRequestAllowButton()
assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
countDown.await()
// Result should contain only the microphone request
Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
}
@Test
fun legacyAppCannotExplicitlyRequestNotifications() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(createChannels = false, requestNotificationPermission = true)
try {
clickPermissionRequestAllowButton(timeoutMillis = EXPECTED_TIMEOUT_MS)
Assert.fail("Expected not to find permission request dialog")
} catch (expected: RuntimeException) {
// Do nothing
}
}
private fun assertAppPermissionGrantedState(permission: String, granted: Boolean) {
SystemUtil.eventually {
runWithShellPermissionIdentity {
Assert.assertEquals("Expected $permission to be granted", context.packageManager
.checkPermission(permission, APP_PACKAGE_NAME), PERMISSION_GRANTED)
}
}
}
private fun launchApp(
createChannels: Boolean = true,
createChannelsDelayed: Boolean = false,
requestNotificationPermission: Boolean = false,
requestPermissions: Boolean = false,
requestPermissionsDelayed: Boolean = false,
launcherCategory: Boolean = true,
mainIntent: Boolean = true,
isEligibleForPromptOption: Boolean = false,
startSecondActivity: Boolean = false,
startSecondaryAppAndCreateChannelsAfterSecondStart: Boolean = false
) {
val intent = if (mainIntent && launcherCategory) {
packageManager.getLaunchIntentForPackage(APP_PACKAGE_NAME)!!
} else if (mainIntent) {
Intent(Intent.ACTION_MAIN)
} else {
Intent(INTENT_ACTION)
}
intent.`package` = APP_PACKAGE_NAME
intent.putExtra(EXTRA_CREATE_CHANNELS, createChannels)
if (!createChannels) {
intent.putExtra(EXTRA_CREATE_CHANNELS_DELAYED, createChannelsDelayed)
}
intent.putExtra(EXTRA_REQUEST_OTHER_PERMISSIONS, requestPermissions)
if (!requestPermissions) {
intent.putExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, requestPermissionsDelayed)
}
intent.putExtra(EXTRA_REQUEST_NOTIF_PERMISSION, requestNotificationPermission)
intent.putExtra(EXTRA_START_SECOND_ACTIVITY, startSecondActivity)
intent.putExtra(EXTRA_START_SECOND_APP, startSecondaryAppAndCreateChannelsAfterSecondStart)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val options = ActivityOptions.makeBasic()
options.isEligibleForLegacyPermissionPrompt = isEligibleForPromptOption
context.startActivity(intent, options.toBundle())
waitFindObject(By.textContains(ACTIVITY_LABEL))
waitForIdle()
}
private fun killTestApp() {
pressBack()
pressBack()
runWithShellPermissionIdentity {
val am = context.getSystemService(ActivityManager::class.java)!!
am.forceStopPackage(APP_PACKAGE_NAME)
}
waitForIdle()
}
private fun assertDialogNotShowing(timeoutMillis: Long = EXPECTED_TIMEOUT_MS) {
try {
clickPermissionRequestAllowButton(timeoutMillis)
Assert.fail("Expected not to find permission request dialog")
} catch (expected: RuntimeException) {
// Do nothing
}
}
}