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