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