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