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 package android.systemui.cts
17 
18 import android.os.SystemClock
19 import android.service.notification.NotificationListenerService
20 import android.service.notification.StatusBarNotification
21 import android.util.Log
22 import com.android.compatibility.common.util.ShellUtils.runShellCommand
23 
24 class NotificationListener : NotificationListenerService() {
25 
onNotificationPostednull26     override fun onNotificationPosted(sbn: StatusBarNotification) {
27         if (DEBUG) Log.d(TAG, "onNotificationPosted: $sbn")
28     }
29 
onNotificationRemovednull30     override fun onNotificationRemoved(sbn: StatusBarNotification) {
31         if (DEBUG) Log.d(TAG, "onNotificationRemoved: $sbn")
32     }
33 
onListenerConnectednull34     override fun onListenerConnected() {
35         if (DEBUG) Log.d(TAG, "onListenerConnected")
36         instance = this
37     }
38 
onListenerDisconnectednull39     override fun onListenerDisconnected() {
40         if (DEBUG) Log.d(TAG, "onListenerDisconnected")
41         instance = null
42     }
43 
44     companion object {
45         private const val DEBUG = false
46         private const val TAG = "SystemUi_Cts_NotificationListener"
47 
48         private const val DEFAULT_TIMEOUT = 5_000L
49         private const val DEFAULT_POLL_INTERVAL = 500L
50 
51         private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
52         private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
53                 "cmd notification disallow_listener %s"
54         private const val COMPONENT_NAME = "android.systemui.cts/.NotificationListener"
55 
56         private var instance: NotificationListener? = null
57 
startNotificationListenernull58         fun startNotificationListener(): Boolean {
59             if (instance != null) {
60                 return true
61             }
62 
63             runShellCommand(CMD_NOTIFICATION_ALLOW_LISTENER.format(COMPONENT_NAME))
64 
65             return wait { instance != null }
66         }
67 
stopNotificationListenernull68         fun stopNotificationListener(): Boolean {
69             if (instance == null) {
70                 return true
71             }
72 
73             runShellCommand(CMD_NOTIFICATION_DISALLOW_LISTENER.format(COMPONENT_NAME))
74             return wait { instance == null }
75         }
76 
waitForNotificationToAppearnull77         fun waitForNotificationToAppear(
78             predicate: (StatusBarNotification) -> Boolean
79         ): StatusBarNotification? {
80             instance?.let {
81                 return waitForResult(extractor = {
82                     it.activeNotifications.firstOrNull(predicate)
83                 }).second
84             } ?: throw IllegalStateException("NotificationListenerService is not connected")
85         }
86 
waitForNotificationToDisappearnull87         fun waitForNotificationToDisappear(
88             predicate: (StatusBarNotification) -> Boolean
89         ): Boolean {
90             return instance?.let {
91                 wait { it.activeNotifications.none(predicate) }
92             } ?: throw IllegalStateException("NotificationListenerService is not connected")
93         }
94 
waitnull95         private fun wait(condition: () -> Boolean): Boolean {
96             val (success, _) = waitForResult(extractor = condition, validator = { it })
97             return success
98         }
99 
waitForResultnull100         private fun <R> waitForResult(
101             timeout: Long = DEFAULT_TIMEOUT,
102             interval: Long = DEFAULT_POLL_INTERVAL,
103             extractor: () -> R,
104             validator: (R) -> Boolean = { it != null }
105         ): Pair<Boolean, R?> {
106             val startTime = SystemClock.uptimeMillis()
107             do {
108                 val result = extractor()
109                 if (validator(result)) {
110                     return (true to result)
111                 }
112                 SystemClock.sleep(interval)
113             } while (SystemClock.uptimeMillis() - startTime < timeout)
114 
115             return (false to null)
116         }
117     }
118 }