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.READ_DEVICE_CONFIG
20 import android.Manifest.permission.READ_SAFETY_CENTER_STATUS
21 import android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE
22 import android.content.Context
23 import android.os.Build.VERSION_CODES.TIRAMISU
24 import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
25 import android.os.UserManager
26 import android.platform.test.flag.junit.DeviceFlagsValueProvider
27 import android.safetycenter.SafetyCenterManager
28 import android.safetycenter.SafetyEvent
29 import android.safetycenter.SafetySourceData
30 import android.safetycenter.config.SafetyCenterConfig
31 import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC
32 import android.util.Log
33 import androidx.annotation.RequiresApi
34 import com.android.modules.utils.build.SdkLevel
35 import com.android.permission.flags.Flags
36 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.addOnSafetyCenterDataChangedListenerWithPermission
37 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.clearAllSafetySourceDataForTestsWithPermission
38 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.clearSafetyCenterConfigForTestsWithPermission
39 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
40 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission
41 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.isSafetyCenterEnabledWithPermission
42 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.removeOnSafetyCenterDataChangedListenerWithPermission
43 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetyCenterConfigForTestsWithPermission
44 import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission
45 import com.android.safetycenter.testing.SafetyCenterFlags.isSafetyCenterEnabled
46 import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED
47 import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
48 import com.google.common.util.concurrent.MoreExecutors.directExecutor
49 import org.junit.Assume.assumeTrue
50 
51 /** A class that facilitates settings up Safety Center in tests. */
52 @RequiresApi(TIRAMISU)
53 class SafetyCenterTestHelper(val context: Context) {
54 
55     private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
56     private val userManager = context.getSystemService(UserManager::class.java)!!
57     private val listeners = mutableListOf<SafetyCenterTestListener>()
58 
59     /**
60      * Sets up the state of Safety Center by enabling it on the device and setting default flag
61      * values. To be called before each test.
62      */
setupnull63     fun setup() {
64         Log.d(TAG, "setup")
65         Coroutines.enableDebugging()
66         SafetySourceReceiver.setup()
67         TestActivity.enableHighPriorityAlias()
68         SafetyCenterFlags.setup()
69         if (safetyCenterCanBeToggledUsingDeviceConfig()) {
70             setEnabled(true)
71         }
72     }
73 
74     /** Resets the state of Safety Center. To be called after each test. */
resetnull75     fun reset() {
76         Log.d(TAG, "reset")
77         if (safetyCenterCanBeToggledUsingDeviceConfig()) {
78             setEnabled(true)
79         }
80         listeners.forEach {
81             safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(it)
82             it.cancel()
83         }
84         listeners.clear()
85         safetyCenterManager.clearAllSafetySourceDataForTestsWithPermission()
86         safetyCenterManager.clearSafetyCenterConfigForTestsWithPermission()
87         resetFlags()
88         TestActivity.disableHighPriorityAlias()
89         SafetySourceReceiver.reset()
90         Coroutines.resetDebugging()
91     }
92 
93     /** Enables or disables SafetyCenter based on [value]. */
setEnablednull94     fun setEnabled(value: Boolean) {
95         Log.d(TAG, "setEnabled to $value")
96         val safetyCenterConfig = safetyCenterManager.getSafetyCenterConfigWithPermission()
97         if (safetyCenterConfig == null) {
98             // No broadcasts are dispatched when toggling the flag when SafetyCenter is not
99             // supported by the device. In this case, toggling this flag should end up being a no-op
100             // as Safety Center will remain disabled regardless, but we still toggle it so that this
101             // no-op behavior can be covered.
102             SafetyCenterFlags.isEnabled = value
103             return
104         }
105         if (value == isEnabled()) {
106             Log.d(TAG, "isEnabled is already $value")
107             return
108         }
109         assumeTrue(
110             "Cannot toggle SafetyCenter using DeviceConfig",
111             safetyCenterCanBeToggledUsingDeviceConfig(),
112         )
113         setEnabledWaitingForSafetyCenterBroadcastIdle(value, safetyCenterConfig)
114     }
115 
116     /** Sets the given [SafetyCenterConfig]. */
setConfignull117     fun setConfig(config: SafetyCenterConfig) {
118         Log.d(TAG, "setConfig")
119         require(isEnabled())
120         safetyCenterManager.setSafetyCenterConfigForTestsWithPermission(config)
121     }
122 
123     /**
124      * Adds and returns a [SafetyCenterTestListener] to SafetyCenter.
125      *
126      * @param skipInitialData whether the returned [SafetyCenterTestListener] should receive the
127      *   initial SafetyCenter update
128      */
addListenernull129     fun addListener(skipInitialData: Boolean = true): SafetyCenterTestListener {
130         Log.d(TAG, "addListener")
131         require(isEnabled())
132         val listener = SafetyCenterTestListener()
133         safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
134             directExecutor(),
135             listener,
136         )
137         if (skipInitialData) {
138             listener.receiveSafetyCenterData()
139         }
140         listeners.add(listener)
141         return listener
142     }
143 
144     /** Sets the [SafetySourceData] for the given [safetySourceId]. */
setDatanull145     fun setData(
146         safetySourceId: String,
147         safetySourceData: SafetySourceData?,
148         safetyEvent: SafetyEvent = EVENT_SOURCE_STATE_CHANGED,
149     ) {
150         Log.d(TAG, "setData for $safetySourceId")
151         require(isEnabled())
152         safetyCenterManager.setSafetySourceDataWithPermission(
153             safetySourceId,
154             safetySourceData,
155             safetyEvent,
156         )
157     }
158 
159     /** Dismisses the [SafetyCenterIssue] for the given [safetyCenterIssueId]. */
160     @RequiresApi(UPSIDE_DOWN_CAKE)
dismissSafetyCenterIssuenull161     fun dismissSafetyCenterIssue(safetyCenterIssueId: String) {
162         Log.d(TAG, "dismissSafetyCenterIssue")
163         require(isEnabled())
164         safetyCenterManager.dismissSafetyCenterIssueWithPermission(safetyCenterIssueId)
165     }
166 
resetFlagsnull167     private fun resetFlags() {
168         if (safetyCenterCanBeToggledUsingDeviceConfig()) {
169             setEnabled(SafetyCenterFlags.snapshot.isSafetyCenterEnabled())
170         }
171         SafetyCenterFlags.reset()
172     }
173 
setEnabledWaitingForSafetyCenterBroadcastIdlenull174     private fun setEnabledWaitingForSafetyCenterBroadcastIdle(
175         value: Boolean,
176         safetyCenterConfig: SafetyCenterConfig,
177     ) =
178         callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE, READ_SAFETY_CENTER_STATUS) {
179             val enabledChangedReceiver = SafetyCenterEnabledChangedReceiver(context)
180             SafetyCenterFlags.isEnabled = value
181             // Wait for all ACTION_SAFETY_CENTER_ENABLED_CHANGED broadcasts to be dispatched to
182             // avoid them leaking onto other tests.
183             if (safetyCenterConfig.containsTestSource()) {
184                 Log.d(TAG, "Waiting for test source enabled changed broadcast")
185                 SafetySourceReceiver.receiveSafetyCenterEnabledChanged()
186                 // The explicit ACTION_SAFETY_CENTER_ENABLED_CHANGED broadcast is also sent to the
187                 // dynamically registered receivers.
188                 enabledChangedReceiver.receiveSafetyCenterEnabledChanged()
189             }
190             // Wait for the implicit ACTION_SAFETY_CENTER_ENABLED_CHANGED broadcast. This is because
191             // the test config could later be set in another test. Since broadcasts are dispatched
192             // asynchronously, a wrong sequencing could still cause failures (e.g: 1: flag switched,
193             // 2: test finishes, 3: new test starts, 4: a test config is set, 5: broadcast from 1
194             // dispatched).
195             if (userManager.isSystemUser) {
196                 Log.d(TAG, "Waiting for system enabled changed broadcast")
197                 // The implicit broadcast is only sent to the system user.
198                 enabledChangedReceiver.receiveSafetyCenterEnabledChanged()
199             }
200             enabledChangedReceiver.unregister()
201             // NOTE: We could be using ActivityManager#waitForBroadcastIdle() to achieve the same
202             // thing.
203             // However:
204             // 1. We can't solely rely on this call to wait for the
205             // ACTION_SAFETY_CENTER_ENABLED_CHANGED broadcasts to be dispatched, as the DeviceConfig
206             // listener is called on a background thread (so waitForBroadcastIdle() could
207             // immediately return prior to the listener being called)
208             // 2. waitForBroadcastIdle() sleeps 1s in a loop when the broadcast queue is not empty,
209             // which would slow down our tests significantly
210         }
211 
containsTestSourcenull212     private fun SafetyCenterConfig.containsTestSource(): Boolean =
213         safetySourcesGroups
214             .flatMap { it.safetySources }
<lambda>null215             .filter { it.type != SAFETY_SOURCE_TYPE_STATIC }
<lambda>null216             .any { it.packageName == context.packageName }
217 
isEnablednull218     private fun isEnabled() = safetyCenterManager.isSafetyCenterEnabledWithPermission()
219 
220     companion object {
221         private const val TAG: String = "SafetyCenterTestHelper"
222 
223         /** Returns whether Safety Center can be enabled / disabled using a DeviceConfig flag. */
224         fun safetyCenterCanBeToggledUsingDeviceConfig(): Boolean {
225             val deviceFlagsValueProvider = DeviceFlagsValueProvider()
226             val safetyCenterEnabledNoDeviceConfig =
227                 callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
228                     deviceFlagsValueProvider.getBoolean(
229                         Flags.FLAG_SAFETY_CENTER_ENABLED_NO_DEVICE_CONFIG
230                     )
231                 }
232             return !safetyCenterEnabledNoDeviceConfig || !SdkLevel.isAtLeastU()
233         }
234     }
235 }
236