blob: 975f4a5ab20fa2ede5fd6bd7679740953345d3b7 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.service.appsetid;
import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__APPSETID;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.adservices.appsetid.GetAppSetIdParam;
import android.adservices.appsetid.GetAppSetIdResult;
import android.adservices.appsetid.IGetAppSetIdCallback;
import android.adservices.common.CallerMetadata;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.common.IntFailureSyncCallback;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException;
import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.adservices.service.stats.AdServicesLoggerImpl;
import com.android.adservices.service.stats.ApiCallStats;
import com.android.adservices.service.stats.Clock;
import com.android.adservices.shared.testing.common.ApplicationContextSingletonRule;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.modules.utils.build.SdkLevel;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.stubbing.Answer;
import java.util.concurrent.CountDownLatch;
/** Unit test for {@link com.android.adservices.service.appsetid.AppSetIdServiceImpl}. */
public class AppSetIdServiceImplTest {
private static final String TEST_APP_PACKAGE_NAME =
"com.android.adservices.servicecoreappsetidtest";
private static final String INVALID_PACKAGE_NAME = "com.do_not_exists";
private static final String SOME_SDK_NAME = "SomeSdkName";
private static final int BINDER_CONNECTION_TIMEOUT_MS = 5_000;
private static final String SDK_PACKAGE_NAME = "test_package_name";
private static final String APPSETID_API_ALLOW_LIST =
"com.android.adservices.servicecoreappsetidtest";
private static final int SANDBOX_UID = 25000;
private final Context mContext = ApplicationProvider.getApplicationContext();
private final AdServicesLogger mAdServicesLogger =
Mockito.spy(AdServicesLoggerImpl.getInstance());
private CallerMetadata mCallerMetadata;
private AppSetIdWorker mAppSetIdWorker;
private GetAppSetIdParam mRequest;
private MockitoSession mStaticMockitoSession;
@Mock private PackageManager mPackageManager;
@Mock private Flags mMockFlags;
@Mock private Clock mClock;
@Mock private Context mMockSdkContext;
@Mock private Throttler mMockThrottler;
@Mock private AppSetIdServiceImpl mAppSetIdServiceImpl;
@Mock private AppImportanceFilter mMockAppImportanceFilter;
@Rule
public final ApplicationContextSingletonRule appContext = new ApplicationContextSingletonRule();
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mAppSetIdWorker = Mockito.spy(AppSetIdWorker.getInstance());
Mockito.doReturn(null).when(mAppSetIdWorker).getService();
when(mClock.elapsedRealtime()).thenReturn(150L, 200L);
mCallerMetadata = new CallerMetadata.Builder().setBinderElapsedTimestamp(100L).build();
mRequest =
new GetAppSetIdParam.Builder()
.setAppPackageName(TEST_APP_PACKAGE_NAME)
.setSdkPackageName(SDK_PACKAGE_NAME)
.build();
when(mMockSdkContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0)).thenReturn(Process.myUid());
// Put this test app into bypass list to bypass Allow-list check.
when(mMockFlags.getPpapiAppAllowList()).thenReturn(APPSETID_API_ALLOW_LIST);
// Rate Limit is not reached.
when(mMockThrottler.tryAcquire(
eq(Throttler.ApiKey.APPSETID_API_APP_PACKAGE_NAME), anyString()))
.thenReturn(true);
// Initialize mock static.
mStaticMockitoSession =
ExtendedMockito.mockitoSession().mockStatic(Binder.class).startMocking();
}
@After
public void tearDown() {
mStaticMockitoSession.finishMocking();
}
@Test
public void checkAllowList_emptyAllowList() throws InterruptedException {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
// Empty allow list.
when(mMockFlags.getPpapiAppAllowList()).thenReturn("");
invokeGetAppSetIdAndVerifyError(
mContext, STATUS_CALLER_NOT_ALLOWED, /* checkLoggingStatus */ true);
}
@Test
public void checkThrottler_rateLimitReached_forAppPackageName() throws InterruptedException {
// App calls AppSetId API directly, not via an SDK.
GetAppSetIdParam request =
new GetAppSetIdParam.Builder()
.setAppPackageName(TEST_APP_PACKAGE_NAME)
.setSdkPackageName(SDK_PACKAGE_NAME)
.build();
// Rate Limit Reached.
when(mMockThrottler.tryAcquire(
eq(Throttler.ApiKey.APPSETID_API_APP_PACKAGE_NAME), anyString()))
.thenReturn(false);
// We don't log STATUS_RATE_LIMIT_REACHED for getAppSetId API.
invokeGetAppSetIdAndVerifyError(
mContext, STATUS_RATE_LIMIT_REACHED, request, /* checkLoggingStatus */ false);
}
@Test
public void testEnforceForeground_sandboxCaller() throws Exception {
Assume.assumeTrue(SdkLevel.isAtLeastT()); // Sandbox caller is only applicable on T+
// Mock AppImportanceFilter to throw Exception when invoked. This is to verify getAppSetId()
// doesn't throw if caller is via Sandbox.
doThrow(new WrongCallingApplicationStateException())
.when(mMockAppImportanceFilter)
.assertCallerIsInForeground(
SANDBOX_UID, AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID, SOME_SDK_NAME);
// Mock UID with SDK UID
when(Binder.getCallingUidOrThrow()).thenReturn(SANDBOX_UID);
// Mock Flags with true to enable enforcing foreground check.
doReturn(true).when(mMockFlags).getEnforceForegroundStatusForAppSetId();
// Mock to grant required permissions
// Copied UID calculation from Process.getAppUidForSdkSandboxUid().
final int appCallingUid = SANDBOX_UID - 10000;
when(mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0)).thenReturn(appCallingUid);
// Verify getAppSetId() doesn't throw.
mAppSetIdServiceImpl = createAppSetIdServiceImplInstance_SandboxContext();
runGetAppSetId(mAppSetIdServiceImpl);
verify(mMockAppImportanceFilter, never())
.assertCallerIsInForeground(
SANDBOX_UID, AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID, SOME_SDK_NAME);
}
@Test
public void testEnforceForeground_disableEnforcing() throws Exception {
final int uid = Process.myUid();
// Mock AppImportanceFilter to throw Exception when invoked. This is to verify getAppSetId()
// doesn't throw if enforcing foreground is disabled
doThrow(new WrongCallingApplicationStateException())
.when(mMockAppImportanceFilter)
.assertCallerIsInForeground(
uid, AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID, SOME_SDK_NAME);
// Mock UID with Non-SDK UI
when(Binder.getCallingUidOrThrow()).thenReturn(uid);
// Mock Flags with false to disable enforcing foreground check.
doReturn(false).when(mMockFlags).getEnforceForegroundStatusForAppSetId();
// Mock to grant required permissions
// TODO
when(mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0)).thenReturn(uid);
// Verify getAppSetId() doesn't throw.
mAppSetIdServiceImpl = createTestAppSetIdServiceImplInstance();
runGetAppSetId(mAppSetIdServiceImpl);
verify(mMockAppImportanceFilter, never())
.assertCallerIsInForeground(
uid, AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID, SOME_SDK_NAME);
}
@Test
public void getAppSetId() throws Exception {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
runGetAppSetId(createTestAppSetIdServiceImplInstance());
}
@Test
public void testGetAppSetId_enforceCallingPackage_invalidPackage() throws InterruptedException {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
// A request with an invalid package name.
mRequest =
new GetAppSetIdParam.Builder()
.setAppPackageName(INVALID_PACKAGE_NAME)
.setSdkPackageName(SOME_SDK_NAME)
.build();
invokeGetAppSetIdAndVerifyError(
mContext, STATUS_CALLER_NOT_ALLOWED, mRequest, /* checkLoggingStatus */ true);
}
private void invokeGetAppSetIdAndVerifyError(
Context context, int expectedResultCode, boolean checkLoggingStatus)
throws InterruptedException {
invokeGetAppSetIdAndVerifyError(context, expectedResultCode, mRequest, checkLoggingStatus);
}
private void invokeGetAppSetIdAndVerifyError(
Context context,
int expectedResultCode,
GetAppSetIdParam request,
boolean checkLoggingStatus)
throws InterruptedException {
SyncIGetAppSetIdCallback callback =
new SyncIGetAppSetIdCallback(BINDER_CONNECTION_TIMEOUT_MS);
CountDownLatch logOperationCalledLatch = new CountDownLatch(1);
Mockito.doAnswer(
(Answer<Object>)
invocation -> {
// The method logAPiCallStats is called.
invocation.callRealMethod();
logOperationCalledLatch.countDown();
return null;
})
.when(mAdServicesLogger)
.logApiCallStats(ArgumentMatchers.any(ApiCallStats.class));
mAppSetIdServiceImpl =
new AppSetIdServiceImpl(
context,
mAppSetIdWorker,
mAdServicesLogger,
mClock,
mMockFlags,
mMockThrottler,
mMockAppImportanceFilter);
mAppSetIdServiceImpl.getAppSetId(request, mCallerMetadata, callback);
callback.assertFailed(expectedResultCode);
if (checkLoggingStatus) {
// getAppSetId method finished executing.
logOperationCalledLatch.await();
ArgumentCaptor<ApiCallStats> argument = ArgumentCaptor.forClass(ApiCallStats.class);
verify(mAdServicesLogger).logApiCallStats(argument.capture());
assertThat(argument.getValue().getCode()).isEqualTo(AD_SERVICES_API_CALLED);
assertThat(argument.getValue().getApiClass())
.isEqualTo(AD_SERVICES_API_CALLED__API_CLASS__APPSETID);
assertThat(argument.getValue().getApiName())
.isEqualTo(AD_SERVICES_API_CALLED__API_NAME__GET_APPSETID);
assertThat(argument.getValue().getResultCode()).isEqualTo(expectedResultCode);
assertThat(argument.getValue().getAppPackageName())
.isEqualTo(request.getAppPackageName());
assertThat(argument.getValue().getSdkPackageName())
.isEqualTo(request.getSdkPackageName());
}
}
private void runGetAppSetId(AppSetIdServiceImpl appSetIdServiceImpl) throws Exception {
GetAppSetIdResult expectedGetAppSetIdResult =
new GetAppSetIdResult.Builder()
.setAppSetId("00000000-0000-0000-0000-000000000000")
.setAppSetIdScope(0)
.build();
GetAppSetIdResult getAppSetIdResult = getAppSetIdResults(appSetIdServiceImpl);
assertThat(getAppSetIdResult.getAppSetId())
.isEqualTo(expectedGetAppSetIdResult.getAppSetId());
}
@NonNull
private GetAppSetIdResult getAppSetIdResults(AppSetIdServiceImpl appSetIdServiceImpl)
throws Exception {
// To capture result in inner class, we have to declare final.
SyncIGetAppSetIdCallback callback =
new SyncIGetAppSetIdCallback(BINDER_CONNECTION_TIMEOUT_MS);
appSetIdServiceImpl.getAppSetId(mRequest, mCallerMetadata, callback);
return callback.assertSuccess();
}
@NonNull
private AppSetIdServiceImpl createTestAppSetIdServiceImplInstance() {
return new AppSetIdServiceImpl(
mContext,
mAppSetIdWorker,
mAdServicesLogger,
mClock,
mMockFlags,
mMockThrottler,
mMockAppImportanceFilter);
}
@NonNull
private AppSetIdServiceImpl createAppSetIdServiceImplInstance_SandboxContext() {
return new AppSetIdServiceImpl(
mMockSdkContext,
mAppSetIdWorker,
mAdServicesLogger,
mClock,
mMockFlags,
mMockThrottler,
mMockAppImportanceFilter);
}
private static final class SyncIGetAppSetIdCallback
extends IntFailureSyncCallback<GetAppSetIdResult> implements IGetAppSetIdCallback {
private SyncIGetAppSetIdCallback(int timeout) {
super(timeout);
}
@Override
public void onError(int resultCode) {
onFailure(resultCode);
}
}
}