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.adservices.service.common; 18 19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; 20 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 21 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; 22 23 import static com.android.adservices.service.common.AppImportanceFilterTest.ApiCallStatsSubject.apiCallStats; 24 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED; 25 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS; 26 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE; 27 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_IMPORTANCE_FILTER_IMPORTANCE_CALLER_NOT_ALLOWED_TO_CROSS_USER_BOUNDARIES; 28 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_IMPORTANCE_FILTER_IMPORTANCE_EXCEEDED_THRESHOLD; 29 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__JOIN_CUSTOM_AUDIENCE; 30 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__TOPICS; 31 32 import static com.google.common.truth.Truth.assertWithMessage; 33 34 import static org.junit.Assert.assertThrows; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.verifyNoMoreInteractions; 37 import static org.mockito.Mockito.when; 38 39 import android.adservices.common.AdServicesStatusUtils; 40 import android.app.ActivityManager; 41 import android.content.pm.PackageManager; 42 import android.util.Log; 43 44 import androidx.annotation.Nullable; 45 46 import com.android.adservices.common.AdServicesExtendedMockitoTestCase; 47 import com.android.adservices.common.logging.annotations.ExpectErrorLogUtilWithExceptionCall; 48 import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException; 49 import com.android.adservices.service.stats.AdServicesLogger; 50 import com.android.adservices.service.stats.ApiCallStats; 51 import com.android.modules.utils.build.SdkLevel; 52 import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic; 53 54 import com.google.common.truth.FailureMetadata; 55 import com.google.common.truth.Subject; 56 57 import org.junit.Before; 58 import org.junit.Test; 59 import org.mockito.ArgumentCaptor; 60 import org.mockito.Captor; 61 import org.mockito.Mock; 62 63 import java.util.Arrays; 64 import java.util.Collections; 65 import java.util.List; 66 67 @SpyStatic(SdkLevel.class) 68 public final class AppImportanceFilterTest extends AdServicesExtendedMockitoTestCase { 69 70 private static final int API_NAME = AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS; 71 private static final int APP_UID = 321; 72 private static final String APP_PACKAGE_NAME = "test.package.name"; 73 private static final String SDK_NAME = "sdk.name"; 74 private static final String PROCESS_NAME = "process_name"; 75 76 @Mock private PackageManager mPackageManager; 77 @Mock private ActivityManager mActivityManager; 78 @Mock private AdServicesLogger mAdServiceLogger; 79 @Captor private ArgumentCaptor<ApiCallStats> mApiCallStatsArgumentCaptor; 80 81 private AppImportanceFilter mAppImportanceFilter; 82 83 @Before setUp()84 public void setUp() { 85 mAppImportanceFilter = 86 new AppImportanceFilter( 87 mActivityManager, 88 mPackageManager, 89 mAdServiceLogger, 90 () -> IMPORTANCE_FOREGROUND_SERVICE); 91 } 92 93 @Test testCalledWithForegroundAppPackageName_onSMinus_succeedBySkippingCheck()94 public void testCalledWithForegroundAppPackageName_onSMinus_succeedBySkippingCheck() { 95 mockIsAtLeastT(false); 96 97 // No exception is thrown 98 mAppImportanceFilter.assertCallerIsInForeground(APP_PACKAGE_NAME, API_NAME, SDK_NAME); 99 100 // Should short-circuit without invoking anything 101 verifyNoMoreInteractions(mActivityManager, mAdServiceLogger, mPackageManager); 102 } 103 104 @Test testCalledWithForegroundAppPackageName_succeed()105 public void testCalledWithForegroundAppPackageName_succeed() { 106 mockIsAtLeastT(true); 107 when(mActivityManager.getPackageImportance(APP_PACKAGE_NAME)) 108 .thenReturn(IMPORTANCE_FOREGROUND); 109 110 // No exception is thrown 111 mAppImportanceFilter.assertCallerIsInForeground(APP_PACKAGE_NAME, API_NAME, SDK_NAME); 112 113 verifyNoMoreInteractions(mAdServiceLogger, mPackageManager); 114 } 115 116 @Test testCalledWithForegroundServiceImportanceAppPackageName_succeed()117 public void testCalledWithForegroundServiceImportanceAppPackageName_succeed() { 118 mockIsAtLeastT(true); 119 when(mActivityManager.getPackageImportance(APP_PACKAGE_NAME)) 120 .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); 121 122 // No exception is thrown 123 mAppImportanceFilter.assertCallerIsInForeground(APP_PACKAGE_NAME, API_NAME, SDK_NAME); 124 125 verifyNoMoreInteractions(mAdServiceLogger, mPackageManager); 126 } 127 128 @Test 129 public void testCalledWithLessThanForegroundImportanceAppPackageName_throwsIllegalStateException()130 testCalledWithLessThanForegroundImportanceAppPackageName_throwsIllegalStateException() { 131 mockIsAtLeastT(true); 132 when(mActivityManager.getPackageImportance(APP_PACKAGE_NAME)) 133 .thenReturn(IMPORTANCE_VISIBLE); 134 135 assertThrows( 136 WrongCallingApplicationStateException.class, 137 () -> 138 mAppImportanceFilter.assertCallerIsInForeground( 139 APP_PACKAGE_NAME, API_NAME, SDK_NAME)); 140 141 verifyNoMoreInteractions(mPackageManager); 142 } 143 144 @Test testCalledWithLessThanForegroundImportanceAppPackageName_logsFailure()145 public void testCalledWithLessThanForegroundImportanceAppPackageName_logsFailure() { 146 mockIsAtLeastT(true); 147 when(mActivityManager.getPackageImportance(APP_PACKAGE_NAME)) 148 .thenReturn(IMPORTANCE_VISIBLE); 149 150 assertThrows( 151 WrongCallingApplicationStateException.class, 152 () -> 153 mAppImportanceFilter.assertCallerIsInForeground( 154 APP_PACKAGE_NAME, API_NAME, SDK_NAME)); 155 156 verify(mAdServiceLogger).logApiCallStats(mApiCallStatsArgumentCaptor.capture()); 157 assertWithMessage("") 158 .about(apiCallStats()) 159 .that(mApiCallStatsArgumentCaptor.getValue()) 160 .hasCode(AD_SERVICES_API_CALLED) 161 .hasApiName(API_NAME) 162 .hasResultCode(AdServicesStatusUtils.STATUS_BACKGROUND_CALLER) 163 .hasSdkPackageName(SDK_NAME) 164 .hasAppPackageName(APP_PACKAGE_NAME); 165 verifyNoMoreInteractions(mPackageManager); 166 expect.that(mApiCallStatsArgumentCaptor.getValue().getApiClass()).isEqualTo(0); 167 } 168 169 @Test 170 public void testFailureTryingToRetrievePackageImportancePackageName_throwsIllegalStateException()171 testFailureTryingToRetrievePackageImportancePackageName_throwsIllegalStateException() { 172 mockIsAtLeastT(true); 173 when(mActivityManager.getPackageImportance(APP_PACKAGE_NAME)) 174 .thenThrow( 175 new IllegalStateException("Simulating failure calling activity manager")); 176 177 assertThrows( 178 IllegalStateException.class, 179 () -> 180 mAppImportanceFilter.assertCallerIsInForeground( 181 APP_PACKAGE_NAME, API_NAME, SDK_NAME)); 182 } 183 184 @Test testCalledWithForegroundAppUid_onSMinus_succeedBySkippingCheck()185 public void testCalledWithForegroundAppUid_onSMinus_succeedBySkippingCheck() { 186 mockIsAtLeastT(false); 187 188 // No exception is thrown 189 mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, SDK_NAME); 190 191 // Should short-circuit without invoking anything 192 verifyNoMoreInteractions(mActivityManager, mAdServiceLogger, mPackageManager); 193 } 194 195 @Test testCalledWithForegroundAppUid_succeed()196 public void testCalledWithForegroundAppUid_succeed() { 197 mockIsAtLeastT(true); 198 mockGetUidImportance(APP_UID, IMPORTANCE_FOREGROUND); 199 200 // No exception is thrown 201 mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, SDK_NAME); 202 203 verifyNoMoreInteractions(mAdServiceLogger); 204 } 205 206 @Test testCalledWithForegroundServiceImportanceAppUid_succeed()207 public void testCalledWithForegroundServiceImportanceAppUid_succeed() { 208 mockIsAtLeastT(true); 209 mockGetUidImportance(APP_UID, IMPORTANCE_FOREGROUND_SERVICE); 210 211 // No exception is thrown 212 mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, SDK_NAME); 213 214 verifyNoMoreInteractions(mAdServiceLogger); 215 } 216 217 @Test 218 @ExpectErrorLogUtilWithExceptionCall( 219 ppapiName = AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__TOPICS, 220 errorCode = 221 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_IMPORTANCE_FILTER_IMPORTANCE_EXCEEDED_THRESHOLD, 222 throwable = WrongCallingApplicationStateException.class) testCalledWithLessThanForegroundImportanceAppUid_throwsIllegalStateException()223 public void testCalledWithLessThanForegroundImportanceAppUid_throwsIllegalStateException() { 224 mockIsAtLeastT(true); 225 mockGetUidImportance(APP_UID, IMPORTANCE_VISIBLE); 226 227 assertThrows( 228 WrongCallingApplicationStateException.class, 229 () -> mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, SDK_NAME)); 230 } 231 232 @Test 233 @ExpectErrorLogUtilWithExceptionCall( 234 ppapiName = AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__TOPICS, 235 errorCode = 236 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_IMPORTANCE_FILTER_IMPORTANCE_EXCEEDED_THRESHOLD, 237 throwable = WrongCallingApplicationStateException.class) testCalledWithLessThanForegroundImportanceAppUid_logsFailure()238 public void testCalledWithLessThanForegroundImportanceAppUid_logsFailure() { 239 mockIsAtLeastT(true); 240 mockGetUidImportance(APP_UID, IMPORTANCE_VISIBLE); 241 mockGetPackagesForUid(APP_UID, APP_PACKAGE_NAME); 242 243 assertThrows( 244 IllegalStateException.class, 245 () -> mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, SDK_NAME)); 246 247 verify(mAdServiceLogger).logApiCallStats(mApiCallStatsArgumentCaptor.capture()); 248 assertWithMessage("") 249 .about(apiCallStats()) 250 .that(mApiCallStatsArgumentCaptor.getValue()) 251 .hasCode(AD_SERVICES_API_CALLED) 252 .hasApiName(API_NAME) 253 .hasResultCode(AdServicesStatusUtils.STATUS_BACKGROUND_CALLER) 254 .hasSdkPackageName(SDK_NAME) 255 .hasAppPackageName(APP_PACKAGE_NAME); 256 expect.that(mApiCallStatsArgumentCaptor.getValue().getApiClass()).isEqualTo(0); 257 } 258 259 @Test 260 @ExpectErrorLogUtilWithExceptionCall( 261 ppapiName = AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__TOPICS, 262 errorCode = 263 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_IMPORTANCE_FILTER_IMPORTANCE_EXCEEDED_THRESHOLD, 264 throwable = WrongCallingApplicationStateException.class) testCalledWithLessThanForegroundImportanceAppUidAndNullSdkName_logsFailure()265 public void testCalledWithLessThanForegroundImportanceAppUidAndNullSdkName_logsFailure() { 266 mockIsAtLeastT(true); 267 mockGetUidImportance(APP_UID, IMPORTANCE_VISIBLE); 268 mockGetPackagesForUid(APP_UID, APP_PACKAGE_NAME); 269 270 assertThrows( 271 WrongCallingApplicationStateException.class, 272 () -> mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, null)); 273 274 verify(mAdServiceLogger).logApiCallStats(mApiCallStatsArgumentCaptor.capture()); 275 assertWithMessage("") 276 .about(apiCallStats()) 277 .that(mApiCallStatsArgumentCaptor.getValue()) 278 .hasCode(AD_SERVICES_API_CALLED) 279 .hasApiName(API_NAME) 280 .hasResultCode(AdServicesStatusUtils.STATUS_BACKGROUND_CALLER) 281 .hasSdkPackageName("") 282 .hasAppPackageName(APP_PACKAGE_NAME); 283 expect.that(mApiCallStatsArgumentCaptor.getValue().getApiClass()).isEqualTo(0); 284 } 285 286 @Test testFailureTryingToRetrievePackageImportanceFromUid_throwsIllegalStateException()287 public void testFailureTryingToRetrievePackageImportanceFromUid_throwsIllegalStateException() { 288 mockIsAtLeastT(true); 289 mockGetUidImportance( 290 APP_UID, new IllegalStateException("Simulating failure calling activity manager")); 291 292 assertThrows( 293 IllegalStateException.class, 294 () -> mAppImportanceFilter.assertCallerIsInForeground(APP_UID, API_NAME, SDK_NAME)); 295 } 296 297 @Test 298 @ExpectErrorLogUtilWithExceptionCall( 299 throwable = SecurityException.class, 300 ppapiName = AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__JOIN_CUSTOM_AUDIENCE, 301 errorCode = 302 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__APP_IMPORTANCE_FILTER_IMPORTANCE_CALLER_NOT_ALLOWED_TO_CROSS_USER_BOUNDARIES) 303 public void testSecurityExceptionTryingToRetrievePackageImportanceFromUid_throwsWrongCallingApplicationStateException()304 testSecurityExceptionTryingToRetrievePackageImportanceFromUid_throwsWrongCallingApplicationStateException() { 305 mockIsAtLeastT(true); 306 mockGetUidImportance(APP_UID, new SecurityException("No can do")); 307 308 WrongCallingApplicationStateException thrown = 309 assertThrows( 310 WrongCallingApplicationStateException.class, 311 () -> 312 mAppImportanceFilter.assertCallerIsInForeground( 313 APP_UID, 314 AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE, 315 SDK_NAME)); 316 317 assertWithMessage("exception message") 318 .that(thrown) 319 .hasMessageThat() 320 .contains( 321 AdServicesStatusUtils 322 .SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_TO_CROSS_USER_BOUNDARIES); 323 324 verify(mAdServiceLogger).logApiCallStats(mApiCallStatsArgumentCaptor.capture()); 325 assertWithMessage("") 326 .about(apiCallStats()) 327 .that(mApiCallStatsArgumentCaptor.getValue()) 328 .hasCode(AD_SERVICES_API_CALLED) 329 .hasApiName(AD_SERVICES_API_CALLED__API_NAME__JOIN_CUSTOM_AUDIENCE) 330 .hasResultCode( 331 AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED_TO_CROSS_USER_BOUNDARIES) 332 .hasSdkPackageName(SDK_NAME) 333 .hasAppPackageName(AppImportanceFilter.UNKNOWN_APP_PACKAGE_NAME); 334 expect.that(mApiCallStatsArgumentCaptor.getValue().getApiClass()).isEqualTo(0); 335 } 336 mockIsAtLeastT(boolean isIt)337 private void mockIsAtLeastT(boolean isIt) { 338 mocker.mockIsAtLeastT(isIt); 339 } 340 mockGetUidImportance(int uid, int result)341 private void mockGetUidImportance(int uid, int result) { 342 Log.v(mTag, "mocking pm.getUidImportance(" + uid + ") returning " + result); 343 when(mActivityManager.getUidImportance(uid)).thenReturn(result); 344 } 345 mockGetUidImportance(int uid, RuntimeException result)346 private void mockGetUidImportance(int uid, RuntimeException result) { 347 Log.v(mTag, "mocking pm.getUidImportance(" + uid + ") throwing " + result); 348 when(mActivityManager.getUidImportance(uid)).thenThrow(result); 349 } 350 mockGetPackagesForUid(int uid, String... packages)351 private void mockGetPackagesForUid(int uid, String... packages) { 352 Log.v( 353 mTag, 354 "mocking pm.getPackagesForUid(" + uid + ") returning " + Arrays.toString(packages)); 355 when(mPackageManager.getPackagesForUid(uid)).thenReturn(packages); 356 } 357 358 /** Helper to generate a process info object */ generateProcessInfo( String packageName, int importance, int uid)359 private static ActivityManager.RunningAppProcessInfo generateProcessInfo( 360 String packageName, int importance, int uid) { 361 return generateProcessInfo(Collections.singletonList(packageName), importance, uid); 362 } 363 generateProcessInfo( List<String> packageNames, int importance, int uid)364 private static ActivityManager.RunningAppProcessInfo generateProcessInfo( 365 List<String> packageNames, int importance, int uid) { 366 ActivityManager.RunningAppProcessInfo process = 367 new ActivityManager.RunningAppProcessInfo( 368 PROCESS_NAME, 100, packageNames.toArray(new String[0])); 369 process.importance = importance; 370 process.uid = uid; 371 return process; 372 } 373 374 public static final class ApiCallStatsSubject extends Subject { apiCallStats()375 public static Factory<ApiCallStatsSubject, ApiCallStats> apiCallStats() { 376 return ApiCallStatsSubject::new; 377 } 378 379 @Nullable private final ApiCallStats mActual; 380 ApiCallStatsSubject(FailureMetadata metadata, @Nullable Object actual)381 ApiCallStatsSubject(FailureMetadata metadata, @Nullable Object actual) { 382 super(metadata, actual); 383 this.mActual = (ApiCallStats) actual; 384 } 385 hasCode(int code)386 public ApiCallStatsSubject hasCode(int code) { 387 check("getCode()").that(mActual.getCode()).isEqualTo(code); 388 return this; 389 } 390 hasApiClass(int apiClass)391 public ApiCallStatsSubject hasApiClass(int apiClass) { 392 check("getApiClass()").that(mActual.getApiClass()).isEqualTo(apiClass); 393 return this; 394 } 395 hasApiName(int apiName)396 public ApiCallStatsSubject hasApiName(int apiName) { 397 check("getApiName()").that(mActual.getApiName()).isEqualTo(apiName); 398 return this; 399 } 400 hasResultCode(int resultCode)401 public ApiCallStatsSubject hasResultCode(int resultCode) { 402 check("getResultCode()").that(mActual.getResultCode()).isEqualTo(resultCode); 403 return this; 404 } 405 hasSdkPackageName(String sdkPackageName)406 public ApiCallStatsSubject hasSdkPackageName(String sdkPackageName) { 407 check("getSdkPackageName()") 408 .that(mActual.getSdkPackageName()) 409 .isEqualTo(sdkPackageName); 410 return this; 411 } 412 hasAppPackageName(String sdkPackageName)413 public ApiCallStatsSubject hasAppPackageName(String sdkPackageName) { 414 check("getAppPackageName()") 415 .that(mActual.getAppPackageName()) 416 .isEqualTo(sdkPackageName); 417 return this; 418 } 419 } 420 } 421