• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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