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