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 package com.android.bedstead.nene.notifications 17 18 import android.annotation.SuppressLint 19 import android.app.NotificationManager 20 import android.content.ComponentName 21 import android.os.Build 22 import com.android.bedstead.nene.TestApis 23 import com.android.bedstead.nene.exceptions.AdbException 24 import com.android.bedstead.nene.exceptions.NeneException 25 import com.android.bedstead.nene.packages.ComponentReference 26 import com.android.bedstead.permissions.CommonPermissions.MANAGE_NOTIFICATION_LISTENERS 27 import com.android.bedstead.nene.users.UserReference 28 import com.android.bedstead.nene.utils.ShellCommand 29 import com.android.bedstead.nene.utils.Tags 30 import com.android.bedstead.nene.utils.Versions 31 import java.util.Collections 32 import java.util.concurrent.ConcurrentHashMap 33 34 /** Helper methods related to notifications. */ 35 object Notifications { 36 37 private val mRegisteredListeners = 38 Collections.newSetFromMap(ConcurrentHashMap<NotificationListener, Boolean>()) 39 private var mListenerAccessIsGranted = false 40 41 /** 42 * Creates a [NotificationListener]. 43 * 44 * 45 * This is required before interacting with notifications in any way. It is recommended that 46 * you do this in a try() block so that the [NotificationListener] closes when you are 47 * finished with it. 48 */ 49 fun createListener(): NotificationListener { 50 if (Tags.hasTag(Tags.USES_DEVICESTATE) && !Tags.hasTag(Tags.USES_NOTIFICATIONS)) { 51 throw NeneException( 52 "Tests which use Notifications must be annotated @NotificationsTest" 53 ) 54 } 55 val notificationListener = NotificationListener(this) 56 mRegisteredListeners.add(notificationListener) 57 initListenerIfRequired() 58 return notificationListener 59 } 60 61 fun removeListener(listener: NotificationListener) { 62 mRegisteredListeners.remove(listener) 63 teardownListenerIfRequired() 64 } 65 66 private fun initListenerIfRequired() { 67 if (mRegisteredListeners.isEmpty()) { 68 return 69 } 70 if (mListenerAccessIsGranted) { 71 return 72 } 73 mListenerAccessIsGranted = true 74 setNotificationListenerAccessGranted( 75 LISTENER_COMPONENT, granted = true, TestApis.users().instrumented() 76 ) 77 } 78 79 private fun teardownListenerIfRequired() { 80 if (!mRegisteredListeners.isEmpty()) { 81 return 82 } 83 if (!mListenerAccessIsGranted) { 84 return 85 } 86 mListenerAccessIsGranted = false 87 setNotificationListenerAccessGranted( 88 LISTENER_COMPONENT, granted = false, TestApis.users().instrumented() 89 ) 90 } 91 92 internal fun onNotificationPosted(notification: Notification) { 93 for (notificationListener in mRegisteredListeners) { 94 notificationListener.onNotificationPosted(notification) 95 } 96 } 97 98 /** 99 * See [NotificationManager.setNotificationListenerAccessGranted]. 100 */ 101 @SuppressLint("NewApi") 102 fun setNotificationListenerAccessGranted( 103 listener: ComponentReference, granted: Boolean, user: UserReference 104 ) { 105 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) { 106 val command = if (granted) "allow_listener" else "disallow_listener" 107 try { 108 ShellCommand.builder("cmd notification") 109 .addOperand(command) 110 .addOperand(listener.componentName().flattenToShortString()) 111 .allowEmptyOutput(true) 112 .validate { obj: String -> obj.isEmpty() } 113 .execute() 114 } catch (e: AdbException) { 115 throw NeneException( 116 "Error setting notification listener access $granted", e 117 ) 118 } 119 return 120 } 121 122 TestApis.permissions().withPermission(MANAGE_NOTIFICATION_LISTENERS).use { 123 TestApis.context().androidContextAsUser(user) 124 .getSystemService(NotificationManager::class.java)!! 125 .setNotificationListenerAccessGranted( 126 listener.componentName(), granted, /* userSet= */false 127 ) 128 } 129 } 130 131 @JvmField 132 val LISTENER_COMPONENT: ComponentReference = TestApis.packages().component( 133 ComponentName( 134 TestApis.context().instrumentedContext().packageName, 135 NeneNotificationListenerService::class.java.canonicalName 136 ) 137 ) 138 } 139