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