• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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