1 /* 2 * Copyright (C) 2022 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 17 package com.android.safetycenter.testing 18 19 import android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE 20 import android.app.Notification 21 import android.app.NotificationChannel 22 import android.app.NotificationManager 23 import android.app.NotificationManager.IMPORTANCE_DEFAULT 24 import android.app.Service 25 import android.content.BroadcastReceiver 26 import android.content.ComponentName 27 import android.content.Context 28 import android.content.Intent 29 import android.content.pm.PackageManager 30 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED 31 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED 32 import android.os.Build.VERSION_CODES.TIRAMISU 33 import android.os.IBinder 34 import android.safetycenter.SafetyCenterManager 35 import android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED 36 import androidx.annotation.RequiresApi 37 import androidx.test.core.app.ApplicationProvider 38 import com.android.compatibility.common.util.SystemUtil 39 import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG 40 import com.android.safetycenter.testing.Coroutines.runBlockingWithTimeout 41 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission 42 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.executeSafetyCenterIssueActionWithPermission 43 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.refreshSafetySourcesWithPermission 44 import com.android.safetycenter.testing.SafetySourceIntentHandler.Request 45 import com.android.safetycenter.testing.SafetySourceIntentHandler.Response 46 import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity 47 import java.time.Duration 48 import kotlinx.coroutines.CoroutineScope 49 import kotlinx.coroutines.Dispatchers 50 import kotlinx.coroutines.Job 51 import kotlinx.coroutines.launch 52 53 /** Broadcast receiver used for testing broadcasts sent to safety sources. */ 54 @RequiresApi(TIRAMISU) 55 class SafetySourceReceiver : BroadcastReceiver() { onReceivenull56 override fun onReceive(context: Context, intent: Intent?) { 57 if (intent == null) { 58 throw IllegalArgumentException("Received null intent") 59 } 60 61 if (runInForegroundService) { 62 context.startForegroundService(intent.setClass(context, ForegroundService::class.java)) 63 } else { 64 runBlockingWithTimeout { safetySourceIntentHandler.handle(context, intent) } 65 } 66 } 67 68 /** 69 * A foreground [Service] that handles incoming intents in the same way as 70 * [SafetySourceReceiver]. 71 * 72 * This is used to verify that [SafetySourceReceiver] can start foreground services. Broadcast 73 * receivers are typically not allowed to do that, but Safety Center uses special broadcast 74 * options that allow safety source receivers to process lengthy operations in foreground 75 * services instead. 76 */ 77 class ForegroundService : Service() { 78 private val serviceJob = Job() 79 private val serviceScope = CoroutineScope(Dispatchers.Default + serviceJob) 80 onBindnull81 override fun onBind(intent: Intent?): IBinder? = null 82 83 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 84 val notificationManager = getSystemService(NotificationManager::class.java)!! 85 notificationManager.createNotificationChannel( 86 NotificationChannel( 87 NOTIFICATION_CHANNEL_ID, 88 NOTIFICATION_CHANNEL_ID, 89 IMPORTANCE_DEFAULT 90 ) 91 ) 92 startForeground( 93 NOTIFICATION_ID, 94 Notification.Builder(this, NOTIFICATION_CHANNEL_ID) 95 .setContentTitle("SafetySourceReceiver") 96 .setContentText( 97 "SafetySourceReceiver is processing an incoming intent in its " + 98 "ForegroundService" 99 ) 100 .setSmallIcon(android.R.drawable.ic_info) 101 .build() 102 ) 103 serviceScope.launch { 104 try { 105 safetySourceIntentHandler.handle(this@ForegroundService, intent!!) 106 } finally { 107 stopForeground(STOP_FOREGROUND_REMOVE) 108 } 109 } 110 super.onStartCommand(intent, flags, startId) 111 return START_NOT_STICKY 112 } 113 onDestroynull114 override fun onDestroy() { 115 serviceJob.cancel() 116 super.onDestroy() 117 } 118 119 companion object { 120 private const val NOTIFICATION_ID: Int = 1204 121 private const val NOTIFICATION_CHANNEL_ID: String = "NOTIFICATION_CHANNEL_ID" 122 } 123 } 124 125 companion object { 126 @Volatile private var safetySourceIntentHandler = SafetySourceIntentHandler() 127 128 /** Whether this receiver should handle incoming intents in a foreground service instead. */ 129 @Volatile var runInForegroundService = false 130 131 /** Enables the [SafetySourceReceiver] for the user running the test. */ setupnull132 fun setup() { 133 setComponentEnabledState(true) 134 } 135 136 /** Resets the state of the [SafetySourceReceiver] between tests. */ resetnull137 fun reset() { 138 setComponentEnabledState(false) 139 safetySourceIntentHandler.cancel() 140 safetySourceIntentHandler = SafetySourceIntentHandler() 141 runInForegroundService = false 142 } 143 setComponentEnabledStatenull144 private fun setComponentEnabledState(enabled: Boolean) { 145 val componentName = 146 ComponentName(getApplicationContext(), SafetySourceReceiver::class.java) 147 getApplicationContext() 148 .packageManager 149 .setComponentEnabledSetting( 150 componentName, 151 if (enabled) COMPONENT_ENABLED_STATE_ENABLED 152 else COMPONENT_ENABLED_STATE_DISABLED, 153 PackageManager.DONT_KILL_APP 154 ) 155 } 156 157 /** 158 * Sets the given [SafetySourceIntentHandler] [response] for the given [request] on this 159 * receiver. 160 */ setResponsenull161 fun setResponse(request: Request, response: Response) { 162 runBlockingWithTimeout { safetySourceIntentHandler.setResponse(request, response) } 163 } 164 SafetyCenterManagernull165 fun SafetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( 166 refreshReason: Int, 167 timeout: Duration = TIMEOUT_LONG, 168 safetySourceIds: List<String>? = null 169 ) = 170 callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) { 171 refreshSafetySourcesWithoutReceiverPermissionAndWait( 172 refreshReason, 173 timeout, 174 safetySourceIds 175 ) 176 } 177 SafetyCenterManagernull178 fun SafetyCenterManager.refreshSafetySourcesWithoutReceiverPermissionAndWait( 179 refreshReason: Int, 180 timeout: Duration, 181 safetySourceIds: List<String>? = null 182 ): String { 183 refreshSafetySourcesWithPermission(refreshReason, safetySourceIds) 184 if (timeout < TIMEOUT_LONG) { 185 SystemUtil.waitForBroadcasts() 186 } 187 return receiveRefreshSafetySources(timeout) 188 } 189 setSafetyCenterEnabledWithReceiverPermissionAndWaitnull190 fun setSafetyCenterEnabledWithReceiverPermissionAndWait( 191 value: Boolean, 192 timeout: Duration = TIMEOUT_LONG 193 ) = 194 callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) { 195 setSafetyCenterEnabledWithoutReceiverPermissionAndWait(value, timeout) 196 } 197 setSafetyCenterEnabledWithoutReceiverPermissionAndWaitnull198 fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait( 199 value: Boolean, 200 timeout: Duration = TIMEOUT_LONG 201 ): Boolean { 202 SafetyCenterFlags.isEnabled = value 203 if (timeout < TIMEOUT_LONG) { 204 SystemUtil.waitForBroadcasts() 205 } 206 return receiveSafetyCenterEnabledChanged(timeout) 207 } 208 SafetyCenterManagernull209 fun SafetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait( 210 issueId: String, 211 issueActionId: String, 212 timeout: Duration = TIMEOUT_LONG 213 ) { 214 callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) { 215 executeSafetyCenterIssueActionWithPermission(issueId, issueActionId) 216 receiveResolveAction(timeout) 217 } 218 } 219 SafetyCenterManagernull220 fun SafetyCenterManager.dismissSafetyCenterIssueWithPermissionAndWait( 221 issueId: String, 222 timeout: Duration = TIMEOUT_LONG 223 ) { 224 callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) { 225 dismissSafetyCenterIssueWithPermission(issueId) 226 receiveDismissIssue(timeout) 227 } 228 } 229 receiveRefreshSafetySourcesnull230 private fun receiveRefreshSafetySources(timeout: Duration = TIMEOUT_LONG): String = 231 runBlockingWithTimeout(timeout) { 232 safetySourceIntentHandler.receiveRefreshSafetySources() 233 } 234 235 /** 236 * Waits for an [ACTION_SAFETY_CENTER_ENABLED_CHANGED] to be received by this receiver 237 * within the given [timeout]. 238 */ receiveSafetyCenterEnabledChangednull239 fun receiveSafetyCenterEnabledChanged(timeout: Duration = TIMEOUT_LONG): Boolean = 240 runBlockingWithTimeout(timeout) { 241 safetySourceIntentHandler.receiveSafetyCenterEnabledChanged() 242 } 243 244 /** Waits for this receiver to resolve an action within the given [timeout]. */ receiveResolveActionnull245 fun receiveResolveAction(timeout: Duration = TIMEOUT_LONG) { 246 runBlockingWithTimeout(timeout) { safetySourceIntentHandler.receiveResolveAction() } 247 } 248 receiveDismissIssuenull249 private fun receiveDismissIssue(timeout: Duration = TIMEOUT_LONG) { 250 runBlockingWithTimeout(timeout) { safetySourceIntentHandler.receiveDimissIssue() } 251 } 252 getApplicationContextnull253 private fun getApplicationContext(): Context = ApplicationProvider.getApplicationContext() 254 } 255 } 256