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 }