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 android.permission.cts; 18 19 import static android.os.Process.myUserHandle; 20 import static android.permission.cts.TestUtils.eventually; 21 22 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 23 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 24 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assume.assumeFalse; 28 import static org.junit.Assume.assumeTrue; 29 30 import static java.util.concurrent.TimeUnit.SECONDS; 31 32 import android.app.NotificationManager; 33 import android.app.UiAutomation; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.pm.PackageManager; 37 import android.provider.DeviceConfig; 38 import android.service.notification.NotificationListenerService; 39 import android.service.notification.StatusBarNotification; 40 import android.util.Log; 41 42 import androidx.test.InstrumentationRegistry; 43 44 import com.android.compatibility.common.util.DeviceConfigStateChangerRule; 45 46 import org.junit.AfterClass; 47 import org.junit.BeforeClass; 48 import org.junit.Rule; 49 50 import java.util.List; 51 52 /** 53 * Base test class used for {@code NotificationListenerCheckTest} and 54 * {@code NotificationListenerCheckWithSafetyCenterUnsupportedTest} 55 */ 56 public class BaseNotificationListenerCheckTest { 57 private static final String LOG_TAG = BaseNotificationListenerCheckTest.class.getSimpleName(); 58 private static final boolean DEBUG = false; 59 60 protected static final String TEST_APP_PKG = 61 "android.permission.cts.appthathasnotificationlistener"; 62 private static final String TEST_APP_NOTIFICATION_SERVICE = 63 TEST_APP_PKG + ".CtsNotificationListenerService"; 64 protected static final String TEST_APP_NOTIFICATION_LISTENER_APK = 65 "/data/local/tmp/cts-permission/CtsAppThatHasNotificationListener.apk"; 66 67 private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4; 68 69 /** 70 * Device config property for whether notification listener check is enabled on the device 71 */ 72 private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED = 73 "notification_listener_check_enabled"; 74 75 /** 76 * Device config property for time period in milliseconds after which current enabled 77 * notification 78 * listeners are queried 79 */ 80 protected static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS = 81 "notification_listener_check_interval_millis"; 82 83 protected static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS = 84 SECONDS.toMillis(0); 85 86 private static final String ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK = 87 "com.android.permissioncontroller.action.SET_UP_NOTIFICATION_LISTENER_CHECK"; 88 private static final String NotificationListenerOnBootReceiver = 89 "com.android.permissioncontroller.privacysources.SetupPeriodicNotificationListenerCheck"; 90 91 /** 92 * ID for notification shown by 93 * {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck}. 94 */ 95 public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3; 96 97 protected static final long UNEXPECTED_TIMEOUT_MILLIS = 10000; 98 protected static final long ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS = 5000; 99 100 private static final Context sContext = InstrumentationRegistry.getTargetContext(); 101 private static final PackageManager sPackageManager = sContext.getPackageManager(); 102 private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation() 103 .getUiAutomation(); 104 105 private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager() 106 .getPermissionControllerPackageName(); 107 108 private static List<ComponentName> sPreviouslyEnabledNotificationListeners; 109 110 // Override SafetyCenter enabled flag 111 @Rule 112 public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled = 113 new DeviceConfigStateChangerRule(sContext, 114 DeviceConfig.NAMESPACE_PRIVACY, 115 SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED, 116 Boolean.toString(true)); 117 118 // Override NlsCheck enabled flag 119 @Rule 120 public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled = 121 new DeviceConfigStateChangerRule(sContext, 122 DeviceConfig.NAMESPACE_PRIVACY, 123 PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED, 124 Boolean.toString(true)); 125 126 // Override general notification interval from once every day to once ever 1 second 127 @Rule 128 public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis = 129 new DeviceConfigStateChangerRule(sContext, 130 DeviceConfig.NAMESPACE_PRIVACY, 131 PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS, 132 Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS)); 133 134 @Rule 135 public CtsNotificationListenerHelperRule ctsNotificationListenerHelper = 136 new CtsNotificationListenerHelperRule(sContext); 137 138 @BeforeClass beforeClassSetup()139 public static void beforeClassSetup() throws Exception { 140 // Disallow any OEM enabled NLS 141 disallowPreexistingNotificationListeners(); 142 } 143 144 @AfterClass afterClassTearDown()145 public static void afterClassTearDown() throws Throwable { 146 // Reallow any previously OEM allowed NLS 147 reallowPreexistingNotificationListeners(); 148 } 149 setDeviceConfigPrivacyProperty(String propertyName, String value)150 protected static void setDeviceConfigPrivacyProperty(String propertyName, String value) { 151 runWithShellPermissionIdentity(() -> { 152 boolean valueWasSet = DeviceConfig.setProperty( 153 DeviceConfig.NAMESPACE_PRIVACY, 154 /* name = */ propertyName, 155 /* value = */ value, 156 /* makeDefault = */ false); 157 if (!valueWasSet) { 158 throw new IllegalStateException("Could not set " + propertyName + " to " + value); 159 } 160 }); 161 } 162 163 /** 164 * Enable or disable notification listener check 165 */ setNotificationListenerCheckEnabled(boolean enabled)166 protected static void setNotificationListenerCheckEnabled(boolean enabled) { 167 setDeviceConfigPrivacyProperty( 168 PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED, 169 /* value = */ String.valueOf(enabled)); 170 } 171 172 /** 173 * Allow or disallow a {@link NotificationListenerService} component for the current user 174 * 175 * @param listenerComponent {@link NotificationListenerService} component to allow or disallow 176 */ setNotificationListenerServiceAllowed(ComponentName listenerComponent, boolean allowed)177 private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent, 178 boolean allowed) { 179 String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ") 180 + listenerComponent.flattenToString(); 181 runShellCommand(command); 182 } 183 disallowPreexistingNotificationListeners()184 private static void disallowPreexistingNotificationListeners() { 185 runWithShellPermissionIdentity(() -> { 186 NotificationManager notificationManager = 187 sContext.getSystemService(NotificationManager.class); 188 sPreviouslyEnabledNotificationListeners = 189 notificationManager.getEnabledNotificationListeners(); 190 }); 191 if (DEBUG) { 192 Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size() 193 + " previously allowed notification listeners. Disabling before test run."); 194 } 195 for (ComponentName listener : sPreviouslyEnabledNotificationListeners) { 196 setNotificationListenerServiceAllowed(listener, false); 197 } 198 } 199 reallowPreexistingNotificationListeners()200 private static void reallowPreexistingNotificationListeners() { 201 if (DEBUG) { 202 Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size() 203 + " previously allowed notification listeners found before test run."); 204 } 205 for (ComponentName listener : sPreviouslyEnabledNotificationListeners) { 206 setNotificationListenerServiceAllowed(listener, true); 207 } 208 } 209 allowTestAppNotificationListenerService()210 protected void allowTestAppNotificationListenerService() { 211 setNotificationListenerServiceAllowed( 212 new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true); 213 } 214 disallowTestAppNotificationListenerService()215 protected void disallowTestAppNotificationListenerService() { 216 setNotificationListenerServiceAllowed( 217 new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false); 218 } 219 220 /** 221 * Force a run of the notification listener check. 222 */ runNotificationListenerCheck()223 protected static void runNotificationListenerCheck() throws Throwable { 224 TestUtils.awaitJobUntilRequestedState( 225 PERMISSION_CONTROLLER_PKG, 226 NOTIFICATION_LISTENER_CHECK_JOB_ID, 227 UNEXPECTED_TIMEOUT_MILLIS, 228 sUiAutomation, 229 "waiting" 230 ); 231 232 TestUtils.runJobAndWaitUntilCompleted( 233 PERMISSION_CONTROLLER_PKG, 234 NOTIFICATION_LISTENER_CHECK_JOB_ID, 235 UNEXPECTED_TIMEOUT_MILLIS, 236 sUiAutomation 237 ); 238 } 239 240 /** 241 * Skip tests for if Safety Center not supported 242 */ assumeDeviceSupportsSafetyCenter()243 protected void assumeDeviceSupportsSafetyCenter() { 244 assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext)); 245 } 246 247 /** 248 * Skip tests for if Safety Center IS supported 249 */ assumeDeviceDoesNotSupportSafetyCenter()250 protected void assumeDeviceDoesNotSupportSafetyCenter() { 251 assumeFalse(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext)); 252 } 253 wakeUpAndDismissKeyguard()254 protected void wakeUpAndDismissKeyguard() { 255 runShellCommand("input keyevent KEYCODE_WAKEUP"); 256 runShellCommand("wm dismiss-keyguard"); 257 } 258 259 /** 260 * Reset the permission controllers state before each test 261 */ resetPermissionControllerBeforeEachTest()262 protected void resetPermissionControllerBeforeEachTest() throws Throwable { 263 resetPermissionController(); 264 265 // ensure no posted notification listener notifications exits 266 eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS); 267 268 // Reset job scheduler stats (to allow more jobs to be run) 269 runShellCommand( 270 "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " " 271 + PERMISSION_CONTROLLER_PKG); 272 runShellCommand("cmd jobscheduler reset-schedule-quota"); 273 } 274 275 /** 276 * Reset the permission controllers state. 277 */ resetPermissionController()278 private static void resetPermissionController() throws Throwable { 279 PermissionUtils.resetPermissionControllerJob(sUiAutomation, PERMISSION_CONTROLLER_PKG, 280 NOTIFICATION_LISTENER_CHECK_JOB_ID, 45000, 281 ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK, NotificationListenerOnBootReceiver); 282 } 283 284 /** 285 * Preshow/dismiss cts NotificationListener notification as it negatively affects test results 286 * (can result in unexpected test pass/failures) 287 */ triggerAndDismissCtsNotificationListenerNotification()288 protected void triggerAndDismissCtsNotificationListenerNotification() throws Throwable { 289 // CtsNotificationListenerService isn't enabled at this point, but NotificationListener 290 // should be. Mark as notified by showing and dismissing 291 runNotificationListenerCheck(); 292 293 // Ensure notification shows and dismiss 294 eventually(() -> assertNotNull(getNotification(true)), 295 UNEXPECTED_TIMEOUT_MILLIS); 296 } 297 298 /** 299 * Get a notification listener notification that is currently visible. 300 * 301 * @param cancelNotification if `true` the notification is canceled inside this method 302 * @return The notification or `null` if there is none 303 */ getNotification(boolean cancelNotification)304 protected StatusBarNotification getNotification(boolean cancelNotification) throws Throwable { 305 return CtsNotificationListenerServiceUtils.getNotificationForPackageAndId( 306 PERMISSION_CONTROLLER_PKG, 307 NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID, 308 cancelNotification); 309 } 310 311 /** 312 * Clear any notifications related to NotificationListenerCheck to ensure clean test setup 313 */ clearNotifications()314 protected void clearNotifications() throws Throwable { 315 // Clear notification if present 316 CtsNotificationListenerServiceUtils.cancelNotifications(PERMISSION_CONTROLLER_PKG); 317 } 318 } 319