blob: b296fa270fd8e7eda7d0816c71f4f151567089f1 [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.tests.cts.topics;
import static com.google.common.truth.Truth.assertThat;
import android.adservices.clients.topics.AdvertisingTopicsClient;
import android.adservices.topics.GetTopicsResponse;
import android.adservices.topics.Topic;
import android.content.Context;
import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.common.AdservicesCtsHelper;
import com.android.compatibility.common.util.ShellUtils;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* Flag "enable_database_schema_version_5" was added to guard Topics API newly added table
* "TopicContributorsTable".
*
* <p>This test is to verify when the flag is changed as false -> true -> false, database will be
* created successfully. That says, it handles DB Upgrade and DB Downgrade correctly.
*/
@RunWith(JUnit4.class)
public class TopicContributorsTableEnableDisableTest {
private static final String LOG_TAG = "adservices";
// The JobId of the Epoch Computation.
private static final int EPOCH_JOB_ID = 2;
// Override the Epoch Job Period to this value to speed up the epoch computation.
private static final long TEST_EPOCH_JOB_PERIOD_MS = 3000;
// As adb commands and broadcast processing require time to execute, add this waiting time to
// allow them to have enough time to be executed. This helps to reduce the test flaky.
private static final long EXECUTION_WAITING_TIME = 300;
// Default Epoch Period.
private static final long TOPICS_EPOCH_JOB_PERIOD_MS = 7 * 86_400_000; // 7 days.
// Use 0 percent for random topic in the test so that we can verify the returned topic.
private static final int TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 0;
private static final int TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 5;
protected static final Context sContext = ApplicationProvider.getApplicationContext();
private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private static final String ADSERVICES_PACKAGE_NAME =
AdservicesCtsHelper.getAdServicesPackageName(sContext, LOG_TAG);
@Before
public void setup() throws Exception {
// Skip the test if it runs on unsupported platforms.
Assume.assumeTrue(AdservicesCtsHelper.isDeviceSupported());
// We need to skip 4 epochs so that if there is any usage from other test runs, it will
// not be used for epoch retrieval.
Thread.sleep(4 * TEST_EPOCH_JOB_PERIOD_MS);
overrideEpochPeriod(TEST_EPOCH_JOB_PERIOD_MS);
// We need to turn off random topic so that we can verify the returned topic.
overridePercentageForRandomTopic(TEST_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
// Override API rate limit to allow consecutive Topics API calls.
overrideApiRateLimit(10);
// Set initial state of TopicsContributorsTable as disabled
enableTopicContributorsTable(false);
// TODO(b/263297331): Handle rollback support for R and S.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
overrideConsentSourceOfTruth(/* PPAPI_ONLY */ 1);
}
}
@After
public void teardown() {
overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
overridePercentageForRandomTopic(TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
overrideApiRateLimit(1);
overrideConsentSourceOfTruth(null);
}
@Test
public void testTableEnabledAndDisabled() throws Exception {
// Default classifier uses the precomputed list first, then on-device classifier.
// The Test App has 2 SDKs: sdk1 calls the Topics API and sdk2 does not.
// Sdk1 calls the Topics API.
AdvertisingTopicsClient advertisingTopicsClient1 =
new AdvertisingTopicsClient.Builder()
.setContext(sContext)
.setSdkName("sdk1")
.setExecutor(CALLBACK_EXECUTOR)
.build();
// At beginning, Sdk1 receives no topic.
GetTopicsResponse sdk1Result = advertisingTopicsClient1.getTopics().get();
assertThat(sdk1Result.getTopics()).isEmpty();
// Now force the Epoch Computation Job. This should be done in the same epoch for
// callersCanLearnMap to have the entry for processing.
forceEpochComputationJob();
// Wait to the next epoch. We will not need to do this after we implement the fix in
// go/rb-topics-epoch-scheduling
Thread.sleep(TEST_EPOCH_JOB_PERIOD_MS);
// Since the sdk1 called the Topics API in the previous Epoch, it should receive some topic.
sdk1Result = advertisingTopicsClient1.getTopics().get();
assertThat(sdk1Result.getTopics()).isNotEmpty();
// We only have 1 test app which has 5 classification topics: 10147,10253,10175,10254,10333
// in the precomputed list.
// These 5 classification topics will become top 5 topics of the epoch since there is
// no other apps calling Topics API.
// The app will be assigned one random topic from one of these 5 topics.
assertThat(sdk1Result.getTopics()).hasSize(1);
Topic topic = sdk1Result.getTopics().get(0);
// topic is one of the 5 classification topics of the Test App.
assertThat(topic.getTopicId()).isIn(Arrays.asList(10147, 10253, 10175, 10254, 10333));
assertThat(topic.getModelVersion()).isAtLeast(1L);
assertThat(topic.getTaxonomyVersion()).isAtLeast(1L);
// Enable TopicContributorsTable and Database should do upgrade.
enableTopicContributorsTable(true);
// Kill Adservices to 1) allow database to re-create 2) clear CacheManager, so it will query
// database again.
killAdServices();
Thread.sleep(EXECUTION_WAITING_TIME);
// Verify database is able to query. Skip checking detailed topics result.
sdk1Result = advertisingTopicsClient1.getTopics().get();
assertThat(sdk1Result.getTopics()).isNotEmpty();
// Disable TopicContributorsTable and Database should do downgrade.
enableTopicContributorsTable(false);
// Kill Adservices to 1) allow database to re-create 2) clear CacheManager, so it will query
// database again.
killAdServices();
Thread.sleep(EXECUTION_WAITING_TIME);
// Verify database is able to query. Skip checking detailed topics result.
sdk1Result = advertisingTopicsClient1.getTopics().get();
assertThat(sdk1Result.getTopics()).isNotEmpty();
}
// Enable/disable TopicContributorsTable
private void enableTopicContributorsTable(boolean isEnabled) {
ShellUtils.runShellCommand(
"device_config put adservices enable_database_schema_version_5 " + isEnabled);
}
// Override API rate limit to allow consecutive Topics API calls.
private void overrideApiRateLimit(int times) {
ShellUtils.runShellCommand(
"setprop debug.adservices.sdk_request_permits_per_second " + times);
}
// Override the Epoch Period to shorten the Epoch Length in the test.
private void overrideEpochPeriod(long overrideEpochPeriod) {
ShellUtils.runShellCommand(
"setprop debug.adservices.topics_epoch_job_period_ms " + overrideEpochPeriod);
}
// Override the Percentage For Random Topic in the test.
private void overridePercentageForRandomTopic(long overridePercentage) {
ShellUtils.runShellCommand(
"setprop debug.adservices.topics_percentage_for_random_topics "
+ overridePercentage);
}
/** Forces JobScheduler to run the Epoch Computation job */
private void forceEpochComputationJob() {
ShellUtils.runShellCommand(
"cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
}
// Force stop AdServices API.
public void killAdServices() {
// adb shell am force-stop com.google.android.adservices.api
ShellUtils.runShellCommand("am force-stop" + " " + ADSERVICES_PACKAGE_NAME);
}
private void overrideConsentSourceOfTruth(Integer value) {
ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth " + value);
}
}