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