blob: 322a2c91b8f0f45c8029f72f4bc7435e7f1393ae [file] [log] [blame]
/*
* Copyright (C) 2022 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.adid;
import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_ID;
import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_PERMISSION_NOT_REQUESTED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_ADID;
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.adid.GetAdIdParam;
import android.adservices.adid.GetAdIdResult;
import android.adservices.adid.IGetAdIdCallback;
import android.adservices.common.CallerMetadata;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.test.mock.MockContext;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
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.Clock;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** Unit test for {@link com.android.adservices.service.adid.AdIdServiceImpl}. */
public class AdIdServiceImplTest {
private static final String TEST_APP_PACKAGE_NAME = "com.android.adservices.servicecoretest";
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 ALLOWED_SDK_ID = "1234567";
// This is not allowed per the ad_services_config.xml manifest config.
private static final String DISALLOWED_SDK_ID = "123";
private static final String ADID_API_ALLOW_LIST = "com.android.adservices.servicecoretest";
private static final int SANDBOX_UID = 25000;
private final Context mContext = ApplicationProvider.getApplicationContext();
private final AdServicesLogger mAdServicesLogger =
Mockito.spy(AdServicesLoggerImpl.getInstance());
private CountDownLatch mGetAdIdCallbackLatch;
private CallerMetadata mCallerMetadata;
private AdIdWorker mAdIdWorker;
private GetAdIdParam mRequest;
private MockitoSession mStaticMockitoSession;
@Mock private PackageManager mPackageManager;
@Mock private Flags mMockFlags;
@Mock private Clock mClock;
@Mock private Context mMockSdkContext;
@Mock private Context mMockAppContext;
@Mock private Throttler mMockThrottler;
@Mock private AdIdServiceImpl mAdIdServiceImpl;
@Mock private AppImportanceFilter mMockAppImportanceFilter;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mAdIdWorker = new AdIdWorker(mContext, mMockFlags);
when(mClock.elapsedRealtime()).thenReturn(150L, 200L);
mCallerMetadata = new CallerMetadata.Builder().setBinderElapsedTimestamp(100L).build();
mRequest =
new GetAdIdParam.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(ADID_API_ALLOW_LIST);
// Rate Limit is not reached.
when(mMockThrottler.tryAcquire(eq(Throttler.ApiKey.ADID_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("");
invokeGetAdIdAndVerifyError(mContext, STATUS_CALLER_NOT_ALLOWED);
}
@Test
public void checkThrottler_rateLimitReached_forAppPackageName() throws InterruptedException {
// App calls AdId API directly, not via an SDK.
GetAdIdParam request =
new GetAdIdParam.Builder()
.setAppPackageName(TEST_APP_PACKAGE_NAME)
.setSdkPackageName(SDK_PACKAGE_NAME)
.build();
// Rate Limit Reached.
when(mMockThrottler.tryAcquire(eq(Throttler.ApiKey.ADID_API_APP_PACKAGE_NAME), anyString()))
.thenReturn(false);
invokeGetAdIdAndVerifyError(mContext, STATUS_RATE_LIMIT_REACHED, request);
}
@Test
public void testEnforceForeground_sandboxCaller() throws Exception {
// Mock AppImportanceFilter to throw Exception when invoked. This is to verify getAdId()
// doesn't throw if caller is via Sandbox.
doThrow(new WrongCallingApplicationStateException())
.when(mMockAppImportanceFilter)
.assertCallerIsInForeground(
SANDBOX_UID, AD_SERVICES_API_CALLED__API_NAME__GET_ADID, 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).getEnforceForegroundStatusForAdId();
// Mock to grant required permissions
// Copied UID calculation from Process.getAppUidForSdkSandboxUid().
final int appCallingUid = SANDBOX_UID - 10000;
when(mPackageManager.checkPermission(ACCESS_ADSERVICES_AD_ID, SDK_PACKAGE_NAME))
.thenReturn(PackageManager.PERMISSION_GRANTED);
when(mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0)).thenReturn(appCallingUid);
// Verify getAdId() doesn't throw.
mAdIdServiceImpl = createAdIdServiceImplInstance_SandboxContext();
runGetAdId(mAdIdServiceImpl);
verify(mMockAppImportanceFilter, never())
.assertCallerIsInForeground(
SANDBOX_UID, AD_SERVICES_API_CALLED__API_NAME__GET_ADID, 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 getAdId()
// doesn't throw if enforcing foreground is disabled
doThrow(new WrongCallingApplicationStateException())
.when(mMockAppImportanceFilter)
.assertCallerIsInForeground(
uid, AD_SERVICES_API_CALLED__API_NAME__GET_ADID, 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).getEnforceForegroundStatusForAdId();
// Mock to grant required permissions
when(mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0)).thenReturn(uid);
// Verify getAdId() doesn't throw.
mAdIdServiceImpl = createTestAdIdServiceImplInstance();
runGetAdId(mAdIdServiceImpl);
verify(mMockAppImportanceFilter, never())
.assertCallerIsInForeground(
uid, AD_SERVICES_API_CALLED__API_NAME__GET_ADID, SOME_SDK_NAME);
}
@Test
public void checkNoPermission() throws InterruptedException {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
MockContext context =
new MockContext() {
@Override
public int checkCallingOrSelfPermission(String permission) {
return PackageManager.PERMISSION_DENIED;
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
};
invokeGetAdIdAndVerifyError(context, STATUS_PERMISSION_NOT_REQUESTED);
}
@Test
public void checkSdkNoPermission() throws InterruptedException {
when(mPackageManager.checkPermission(ACCESS_ADSERVICES_AD_ID, SDK_PACKAGE_NAME))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(Binder.getCallingUidOrThrow()).thenReturn(SANDBOX_UID);
invokeGetAdIdAndVerifyError(mMockSdkContext, STATUS_UNAUTHORIZED);
}
@Test
public void getAdId() throws Exception {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
runGetAdId(createTestAdIdServiceImplInstance());
}
@Test
public void testGetAdId_enforceCallingPackage_invalidPackage() throws InterruptedException {
when(Binder.getCallingUidOrThrow()).thenReturn(Process.myUid());
AdIdServiceImpl adidService = createTestAdIdServiceImplInstance();
// A request with an invalid package name.
mRequest =
new GetAdIdParam.Builder()
.setAppPackageName(INVALID_PACKAGE_NAME)
.setSdkPackageName(SOME_SDK_NAME)
.build();
mGetAdIdCallbackLatch = new CountDownLatch(1);
adidService.getAdId(
mRequest,
mCallerMetadata,
new IGetAdIdCallback() {
@Override
public void onResult(GetAdIdResult responseParcel) {
Assert.fail();
}
@Override
public void onError(int resultCode) {
assertThat(resultCode).isEqualTo(STATUS_CALLER_NOT_ALLOWED);
mGetAdIdCallbackLatch.countDown();
}
@Override
public IBinder asBinder() {
return null;
}
});
// This ensures that the callback was called.
assertThat(mGetAdIdCallbackLatch.await(BINDER_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
.isTrue();
}
private void invokeGetAdIdAndVerifyError(Context context, int expectedResultCode)
throws InterruptedException {
invokeGetAdIdAndVerifyError(context, expectedResultCode, mRequest);
}
private void invokeGetAdIdAndVerifyError(
Context context, int expectedResultCode, GetAdIdParam request)
throws InterruptedException {
CountDownLatch jobFinishedCountDown = new CountDownLatch(1);
mAdIdServiceImpl =
new AdIdServiceImpl(
context,
mAdIdWorker,
mAdServicesLogger,
mClock,
mMockFlags,
mMockThrottler,
mMockAppImportanceFilter);
mAdIdServiceImpl.getAdId(
request,
mCallerMetadata,
new IGetAdIdCallback() {
@Override
public void onResult(GetAdIdResult responseParcel) {
Assert.fail();
jobFinishedCountDown.countDown();
}
@Override
public void onError(int resultCode) {
assertThat(resultCode).isEqualTo(expectedResultCode);
jobFinishedCountDown.countDown();
}
@Override
public IBinder asBinder() {
return null;
}
});
jobFinishedCountDown.await();
}
private void runGetAdId(AdIdServiceImpl adIdServiceImpl) throws Exception {
GetAdIdResult expectedGetAdIdResult =
new GetAdIdResult.Builder()
.setAdId("00000000-0000-0000-0000-000000000000")
.setLatEnabled(false)
.build();
final GetAdIdResult[] capturedResponseParcel = getAdIdResults(adIdServiceImpl);
assertThat(mGetAdIdCallbackLatch.await(BINDER_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
.isTrue();
GetAdIdResult getAdIdResult = capturedResponseParcel[0];
assertThat(getAdIdResult.getAdId()).isEqualTo(expectedGetAdIdResult.getAdId());
}
@NonNull
private GetAdIdResult[] getAdIdResults(AdIdServiceImpl adIdServiceImpl) {
// To capture result in inner class, we have to declare final.
final GetAdIdResult[] capturedResponseParcel = new GetAdIdResult[1];
mGetAdIdCallbackLatch = new CountDownLatch(1);
adIdServiceImpl.getAdId(
mRequest,
mCallerMetadata,
new IGetAdIdCallback() {
@Override
public void onResult(GetAdIdResult responseParcel) {
capturedResponseParcel[0] = responseParcel;
mGetAdIdCallbackLatch.countDown();
}
@Override
public void onError(int resultCode) {
Assert.fail();
}
@Override
public IBinder asBinder() {
return null;
}
});
return capturedResponseParcel;
}
@NonNull
private AdIdServiceImpl createTestAdIdServiceImplInstance() {
return new AdIdServiceImpl(
mContext,
mAdIdWorker,
mAdServicesLogger,
mClock,
mMockFlags,
mMockThrottler,
mMockAppImportanceFilter);
}
@NonNull
private AdIdServiceImpl createAdIdServiceImplInstance_SandboxContext() {
return new AdIdServiceImpl(
mMockSdkContext,
mAdIdWorker,
mAdServicesLogger,
mClock,
mMockFlags,
mMockThrottler,
mMockAppImportanceFilter);
}
}