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