summaryrefslogtreecommitdiff
path: root/adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java')
-rw-r--r--adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java355
1 files changed, 355 insertions, 0 deletions
diff --git a/adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java b/adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java
new file mode 100644
index 000000000..0fe768b0d
--- /dev/null
+++ b/adservices/tests/cts/endtoends/topics/appupdate/src/com/android/adservices/tests/cts/topics/appupdate/AppUpdateTest.java
@@ -0,0 +1,355 @@
+/*
+ * 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.appupdate;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.adservices.clients.topics.AdvertisingTopicsClient;
+import android.adservices.topics.GetTopicsResponse;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * This CTS test is to test app update flow for Topics API. It has two goals:
+ *
+ * <ul>
+ * <li>1. To test topic assignment for newly installed apps
+ * <li>2. To test data wiping-off for uninstalled apps
+ * </ul>
+ *
+ * <p>It communicates with test apps, which are installed/uninstalled, call Topic API, etc. The
+ * basic ideas are
+ *
+ * <ul>
+ * <li>Start test apps from this CTS test suite.
+ * <li>Test apps call Topics API in <code>onCreate()</code> method, and sends out broadcast to
+ * this CTS test suite.
+ * <li>This CTS test suite asserts the expected results in <code>onReceived()</code>
+ * <li>Test apps will be killed once it finishes calling Topic API.
+ * </ul>
+ *
+ * <p>Specific steps:
+ *
+ * <ul>
+ * <li>1. CTS Test Suite app calls topics API so that Topics API has usage at epoch T. Then
+ * trigger epoch computation and get top/returned topics at epoch T.
+ * <li>2. Install test app at epoch T+1 and it'll be assigned with returned topics at T.
+ * <li>3. Forces Maintenance job to run to reconcile app installation mismatching, in case
+ * broadcast is missed due to system delay.
+ * <li>4. Test app itself calls Topics API and it'll return the topics at T. Then test app calls
+ * Topics API through sdk, the sdk will be assigned with returned topic for epoch T at the
+ * serving flow. Both calls will send broadcast to this CTS test suite app and this test suite
+ * app will verify the results are expected
+ * <li>5. Uninstall test app and wait for 3 epochs.
+ * <li>6. Install test app again and it won't be assigned with topics as no usage in the past 3
+ * epoch. Call topics API again via app only and sdk. Both calls should have empty response as
+ * all derived data been wiped off.
+ * <li>7. Finally verify the number of broadcast received is as expected. Then uninstall test app
+ * and unregister the listener.
+ * </ul>
+ *
+ * <p>Expected running time: ~48s.
+ */
+public class AppUpdateTest {
+ @SuppressWarnings("unused")
+ private static final String TAG = "AppUpdateTest";
+
+ private static final String TEST_APK_NAME = "CtsSampleTopicsApp1.apk";
+ private static final String TEST_APK_PATH = "/data/local/tmp/cts/install/" + TEST_APK_NAME;
+ private static final String TEST_PKG_NAME = "com.android.adservices.tests.cts.topics.testapp1";
+ private static final String TEST_ACTIVITY_NAME = TEST_PKG_NAME + ".MainActivity";
+ private static final ComponentName COMPONENT =
+ new ComponentName(TEST_PKG_NAME, TEST_ACTIVITY_NAME);
+
+ // Broadcast sent from test apps to test suite to pass GetTopicsResponse. Use two types of
+ // broadcast to make the result more explicit.
+ private static final String TOPIC_RESPONSE_BROADCAST_KEY = "topicIds";
+ // Test app will send this broadcast to this CTS test suite if it receives non-empty topics.
+ private static final String NON_EMPTY_TOPIC_RESPONSE_BROADCAST =
+ "com.android.adservices.tests.cts.topics.NON_EMPTY_TOPIC_RESPONSE";
+ // Test app will send this broadcast to this CTS test suite if it receives empty topics.
+ private static final String EMPTY_TOPIC_RESPONSE_BROADCAST =
+ "com.android.adservices.tests.cts.topics.EMPTY_TOPIC_RESPONSE";
+ private static final String SDK_NAME = "sdk";
+
+ protected static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
+
+ // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
+ private static final String TOPICS_SERVICE_NAME = "android.adservices.TOPICS_SERVICE";
+ private static final String ADSERVICES_PACKAGE_NAME = getAdServicesPackageName();
+
+ // Expected topic responses used for assertion. This is the expected order of broadcasts that
+ // the test sample app will send back to the main test app. So the order is important.
+ private static final String[] EXPECTED_TOPIC_RESPONSE_BROADCASTS = {
+ NON_EMPTY_TOPIC_RESPONSE_BROADCAST,
+ NON_EMPTY_TOPIC_RESPONSE_BROADCAST,
+ EMPTY_TOPIC_RESPONSE_BROADCAST,
+ EMPTY_TOPIC_RESPONSE_BROADCAST,
+ };
+
+ // The JobId of the Epoch Computation and Maintenance job.
+ private static final int EPOCH_JOB_ID = 2;
+ private static final int MAINTENANCE_JOB_ID = 1;
+
+ // Override the Epoch Job Period to this value to speed up the epoch computation.
+ private static final long TEST_EPOCH_JOB_PERIOD_MS = 5000;
+
+ // 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 = 2000;
+
+ // 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 DEFAULT_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC = 5;
+
+ private int mExpectedTopicResponseBroadCastIndex = 0;
+ private BroadcastReceiver mTopicsResponseReceiver;
+
+ @Before
+ public void setup() throws InterruptedException {
+ // We need to skip 3 epochs so that if there is any usage from other test runs, it will
+ // not be used for epoch retrieval.
+ Thread.sleep(3 * 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);
+
+ registerTopicResponseReceiver();
+ }
+
+ @After
+ public void tearDown() {
+ overrideEpochPeriod(TOPICS_EPOCH_JOB_PERIOD_MS);
+ overridePercentageForRandomTopic(DEFAULT_TOPICS_PERCENTAGE_FOR_RANDOM_TOPIC);
+ }
+
+ @Test
+ public void testAppUpdate() throws Exception {
+ // Invoke Topics API once to compute top topics so that following installed test apps are
+ // able to get top topics assigned when getting installed.
+ AdvertisingTopicsClient advertisingTopicsClient =
+ new AdvertisingTopicsClient.Builder()
+ .setContext(sContext)
+ .setSdkName(SDK_NAME)
+ .setExecutor(CALLBACK_EXECUTOR)
+ .build();
+
+ // At beginning, Sdk1 receives no topic.
+ GetTopicsResponse sdkResponse = advertisingTopicsClient.getTopics().get();
+ assertThat(sdkResponse.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);
+
+ // Install test app1.
+ installTestSampleApp();
+ Thread.sleep(EXECUTION_WAITING_TIME);
+
+ // Forces Maintenance job if broadcast for installation is missed or delayed.
+ forceMaintenanceJob();
+ Thread.sleep(EXECUTION_WAITING_TIME);
+
+ // Invoke test app1. The test app1 should be assigned with topics as there are usages
+ // and top topics in the last 3 epochs.
+ Intent testAppIntent =
+ new Intent().setComponent(COMPONENT).addFlags(FLAG_ACTIVITY_NEW_TASK);
+ sContext.startActivity(testAppIntent);
+ Thread.sleep(EXECUTION_WAITING_TIME);
+
+ // Uninstall test app1. All derived data for test app1 should be wiped off.
+ uninstallTestSampleApp();
+
+ // Skip 3 epochs so that newly installed apps won't be assigned with topics.
+ Thread.sleep(3 * TEST_EPOCH_JOB_PERIOD_MS);
+
+ // Install the test app1 again. It should not be assigned with new topics.
+ installTestSampleApp();
+ Thread.sleep(EXECUTION_WAITING_TIME);
+
+ // Forces Maintenance job if broadcast for installation is missed or delayed.
+ // This is the second verification to justify the test even broadcast is missed.
+ forceMaintenanceJob();
+ Thread.sleep(EXECUTION_WAITING_TIME);
+
+ // Invoke test app1. It should get empty returned topics because its derived data was wiped
+ // off from the uninstallation.
+ sContext.startActivity(testAppIntent);
+ Thread.sleep(EXECUTION_WAITING_TIME);
+
+ // Unregistered the receiver and uninstall the test app1
+ // Note aosp_x86 requires --user 0 to uninstall though arm doesn't.
+ sContext.unregisterReceiver(mTopicsResponseReceiver);
+ uninstallTestSampleApp();
+
+ // Finally, assert that the number of received broadcasts matches with expectation
+ assertThat(mExpectedTopicResponseBroadCastIndex)
+ .isEqualTo(EXPECTED_TOPIC_RESPONSE_BROADCASTS.length);
+ }
+
+ // Broadcast Receiver to receive getTopicResponse broadcast from test apps
+ private void registerTopicResponseReceiver() {
+ final IntentFilter topicResponseIntentFilter = new IntentFilter();
+ topicResponseIntentFilter.addAction(NON_EMPTY_TOPIC_RESPONSE_BROADCAST);
+ topicResponseIntentFilter.addAction(EMPTY_TOPIC_RESPONSE_BROADCAST);
+
+ // Assert the result at each time the CTS test suite receives the broadcast. Specifically,
+ // First time: test app1 should get non-empty returned topics as it was assigned with topics
+ // when it gets installed. This is to test the app installation behaviors.
+ // Second time: test app1 should get non-empty returned topics as it calls Topics API via
+ // sdk. This is to test sdk topics assignment for newly installed apps.
+ // Third time: test app1 should get empty returned topics as it wasn't assigned with topics
+ // when it gets installed due to zero usage in last 3 epochs, as well as it was
+ // uninstalled. This is to test the uninstallation behaviors.
+ // Fourth time: test app1 should also get empty returned topics when it calls Topics API
+ // via sdk.
+ mTopicsResponseReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ // Verify the broadcast type.
+ assertThat(mExpectedTopicResponseBroadCastIndex)
+ .isLessThan(EXPECTED_TOPIC_RESPONSE_BROADCASTS.length);
+ assertThat(action)
+ .isEqualTo(
+ EXPECTED_TOPIC_RESPONSE_BROADCASTS[
+ mExpectedTopicResponseBroadCastIndex]);
+
+ // Verify the returned topics if there is any
+ if (action.equals(NON_EMPTY_TOPIC_RESPONSE_BROADCAST)) {
+ int[] topics =
+ intent.getExtras().getIntArray(TOPIC_RESPONSE_BROADCAST_KEY);
+
+ // topic is one of the 5 classification topics of the Test App.
+ assertThat(topics.length).isEqualTo(1);
+ assertThat(topics[0])
+ .isIn(Arrays.asList(10147, 10253, 10175, 10254, 10333));
+ }
+
+ mExpectedTopicResponseBroadCastIndex++;
+ }
+ };
+
+ sContext.registerReceiver(mTopicsResponseReceiver, topicResponseIntentFilter);
+ }
+
+ // Install test sample app 1 and verify the installation.
+ private void installTestSampleApp() {
+ String installMessage = ShellUtils.runShellCommand("pm install -r " + TEST_APK_PATH);
+ assertThat(installMessage).contains("Success");
+ }
+
+ // Note aosp_x86 requires --user 0 to uninstall though arm doesn't.
+ private void uninstallTestSampleApp() {
+ ShellUtils.runShellCommand("pm uninstall --user 0 " + TEST_PKG_NAME);
+ }
+
+ /** Forces JobScheduler to run the Epoch Computation job */
+ private void forceEpochComputationJob() {
+ ShellUtils.runShellCommand(
+ "cmd jobscheduler run -f" + " " + ADSERVICES_PACKAGE_NAME + " " + EPOCH_JOB_ID);
+ }
+
+ // 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 Maintenance job.
+ private void forceMaintenanceJob() {
+ ShellUtils.runShellCommand(
+ "cmd jobscheduler run -f"
+ + " "
+ + ADSERVICES_PACKAGE_NAME
+ + " "
+ + MAINTENANCE_JOB_ID);
+ }
+
+ // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
+ private static String getAdServicesPackageName() {
+ final Intent intent = new Intent(TOPICS_SERVICE_NAME);
+ final List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager()
+ .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (resolveInfos == null || resolveInfos.isEmpty()) {
+ Log.e(
+ TAG,
+ "Failed to find resolveInfo for adServices service. Intent action: "
+ + TOPICS_SERVICE_NAME);
+ return null;
+ }
+
+ if (resolveInfos.size() > 1) {
+ Log.e(
+ TAG,
+ String.format(
+ "Found multiple services (%1$s) for the same intent action (%2$s)",
+ TOPICS_SERVICE_NAME, resolveInfos));
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
+ if (serviceInfo == null) {
+ Log.e(TAG, "Failed to find serviceInfo for adServices service");
+ return null;
+ }
+
+ return serviceInfo.packageName;
+ }
+}