| /* |
| * 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 android.ext.services.hosttests; |
| |
| import static com.android.adservices.common.TestDeviceHelper.runShellCommand; |
| |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import android.ext.services.hosttests.utils.ExtServicesLogcatReceiver; |
| |
| import com.android.adservices.common.AdServicesHostSideFlagsSetterRule; |
| import com.android.adservices.common.AdServicesHostSideTestCase; |
| import com.android.adservices.common.HostSideSdkLevelSupportRule; |
| import com.android.adservices.common.RequiresSdkLevelAtLeastT; |
| import com.android.adservices.common.RequiresSdkLevelLessThanT; |
| import com.android.adservices.common.TestDeviceHelper; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.device.PackageInfo; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.Arrays; |
| import java.util.Locale; |
| import java.util.function.Predicate; |
| import java.util.regex.Pattern; |
| |
| @RunWith(DeviceJUnit4ClassRunner.class) |
| public class AdServicesFilesCleanupBootCompleteReceiverHostTest extends AdServicesHostSideTestCase { |
| private static final String EXTSERVICES_PACKAGE_SUFFIX = "android.ext.services"; |
| private static final String CLEANUP_RECEIVER_CLASS_NAME = |
| "android.ext.services.common.AdServicesFilesCleanupBootCompleteReceiver"; |
| private static final String LOGCAT_COMMAND = "logcat -s extservices"; |
| private static final String RECEIVER_DISABLED_LOG_TEXT = |
| "Disabled AdServices files cleanup receiver"; |
| private static final String RECEIVER_EXECUTED_LOG_TEXT = "AdServices files cleanup receiver"; |
| |
| private String mAdServicesFilePath; |
| private String mExtServicesPackageName; |
| |
| @Rule |
| public final HostSideSdkLevelSupportRule sdkLevelSupportRule = |
| HostSideSdkLevelSupportRule.forAnyLevel(); |
| |
| @Rule |
| public final AdServicesHostSideFlagsSetterRule flags = |
| AdServicesHostSideFlagsSetterRule.forGlobalKillSwitchDisabledTests() |
| .setLogcatTag("extservices", "VERBOSE"); |
| |
| @Before |
| public void setUp() throws Exception { |
| ITestDevice device = getDevice(); |
| |
| logDeviceMetadata(); |
| |
| // Find the extservices package |
| PackageInfo extServicesPackage = |
| device.getAppPackageInfos().stream() |
| .filter(s -> s.getPackageName().endsWith(EXTSERVICES_PACKAGE_SUFFIX)) |
| .findFirst() |
| .orElse(null); |
| assertWithMessage("ExtServices package").that(extServicesPackage).isNotNull(); |
| mExtServicesPackageName = extServicesPackage.getPackageName(); |
| |
| // Put some data in the ExtServices apk |
| mAdServicesFilePath = |
| String.format( |
| "/data/user/%d/%s/adservices_data.txt", |
| device.getCurrentUser(), extServicesPackage.getPackageName()); |
| runShellCommand("echo \"Hello\" > %s", mAdServicesFilePath); |
| assertWithMessage("%s exists", mAdServicesFilePath) |
| .that(device.doesFileExist(mAdServicesFilePath)) |
| .isTrue(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (mDevice != null && mAdServicesFilePath != null |
| && mDevice.doesFileExist(mAdServicesFilePath)) { |
| mDevice.deleteFile(mAdServicesFilePath); |
| } |
| } |
| |
| @Test |
| @RequiresSdkLevelLessThanT(reason = "Testing functionality that only runs on S-") |
| public void testReceiver_doesNotExecuteOnSMinus() throws Exception { |
| ITestDevice device = getDevice(); |
| |
| // TODO(b/297207132) - use a rule instead of this shell command |
| // Enable the flag that the receiver checks. By default, the flag is enabled in the binary, |
| // so it's enough to just delete the flag override, if any. |
| device.executeShellCommand( |
| "device_config delete adservices extservices_adservices_data_cleanup_enabled"); |
| |
| // Reboot, wait, and verify logs. |
| verifyReceiverDidNotExecute(device); |
| |
| // Verify that adservices files are still present. |
| assertWithMessage("%s exists", mAdServicesFilePath) |
| .that(device.doesFileExist(mAdServicesFilePath)) |
| .isTrue(); |
| } |
| |
| @Test |
| @RequiresSdkLevelAtLeastT(reason = "Testing functionality that only runs on T+") |
| public void testReceiver_deletesFiles() throws Exception { |
| ITestDevice device = getDevice(); |
| |
| // Re-enable the cleanup receiver in case it's been disabled due to a prior run |
| enableReceiver(); |
| |
| // Enable the flag that the receiver checks. By default, the flag is enabled in the binary, |
| // so it's enough to just delete the flag override, if any. |
| device.executeShellCommand( |
| "device_config delete adservices extservices_adservices_data_cleanup_enabled"); |
| |
| // Reboot, wait, and verify logs. |
| verifyReceiverExecuted(device); |
| |
| // Verify that all adservices files were deleted. |
| assertWithMessage("%s exists", mAdServicesFilePath) |
| .that(device.doesFileExist(mAdServicesFilePath)) |
| .isFalse(); |
| |
| String lsCommand = |
| String.format( |
| "ls /data/user/%d/%s -R", device.getCurrentUser(), mExtServicesPackageName); |
| String lsOutput = device.executeShellCommand(lsCommand).toLowerCase(Locale.ROOT); |
| assertWithMessage("Output of %s", lsCommand).that(lsOutput).doesNotContain("adservices"); |
| |
| // Verify that after a reboot the receiver does not execute |
| verifyReceiverDidNotExecute(device); |
| } |
| |
| @Test |
| @RequiresSdkLevelAtLeastT(reason = "Testing functionality that only runs on T+") |
| public void testReceiver_doesNotExecuteIfFlagDisabled() throws Exception { |
| ITestDevice device = getDevice(); |
| |
| // Re-enable the cleanup receiver in case it's been disabled due to a prior run |
| enableReceiver(); |
| |
| // Disable the flag that the receiver checks |
| device.executeShellCommand( |
| "device_config put adservices extservices_adservices_data_cleanup_enabled false"); |
| |
| // Verify that after a reboot the receiver executes but doesn't disable itself |
| ExtServicesLogcatReceiver logcatReceiver = |
| rebootDeviceAndCollectLogs(device, RECEIVER_DISABLED_LOG_TEXT); |
| Pattern errorPattern = Pattern.compile(makePattern(RECEIVER_DISABLED_LOG_TEXT)); |
| assertWithMessage("Presence of log indicating receiver disabled itself") |
| .that(logcatReceiver.patternMatches(errorPattern)) |
| .isFalse(); |
| |
| // Verify that the file is still there and that the receiver didn't delete it. |
| assertWithMessage("%s exists", mAdServicesFilePath) |
| .that(device.doesFileExist(mAdServicesFilePath)) |
| .isTrue(); |
| } |
| |
| private void verifyReceiverExecuted(ITestDevice device) |
| throws DeviceNotAvailableException, InterruptedException { |
| ExtServicesLogcatReceiver logcatReceiver = |
| rebootDeviceAndCollectLogs(device, RECEIVER_DISABLED_LOG_TEXT); |
| Pattern errorPattern = Pattern.compile(makePattern(RECEIVER_DISABLED_LOG_TEXT)); |
| assertWithMessage("Presence of log indicating receiver disabled itself") |
| .that(logcatReceiver.patternMatches(errorPattern)) |
| .isTrue(); |
| } |
| |
| private void verifyReceiverDidNotExecute(ITestDevice device) |
| throws DeviceNotAvailableException, InterruptedException { |
| ExtServicesLogcatReceiver logcatReceiver = |
| rebootDeviceAndCollectLogs(device, RECEIVER_EXECUTED_LOG_TEXT); |
| |
| Pattern errorPattern = Pattern.compile(makePattern(RECEIVER_EXECUTED_LOG_TEXT)); |
| assertWithMessage("Presence of log indicating receiver was invoked") |
| .that(logcatReceiver.patternMatches(errorPattern)) |
| .isFalse(); |
| } |
| |
| private Predicate<String[]> stopIfTextOccurs(String toMatch) { |
| return (s) -> Arrays.stream(s).anyMatch(t -> t.contains(toMatch)); |
| } |
| |
| private ExtServicesLogcatReceiver rebootDeviceAndCollectLogs(ITestDevice device, String text) |
| throws DeviceNotAvailableException, InterruptedException { |
| // reboot the device |
| device.reboot(); |
| device.waitForDeviceAvailable(); |
| |
| flags.setLogcatTag("extservices", "VERBOSE"); |
| |
| // Start log collection |
| ExtServicesLogcatReceiver logcatReceiver = |
| new ExtServicesLogcatReceiver.Builder() |
| .setDevice(device) |
| .setLogCatCommand(LOGCAT_COMMAND) |
| .setEarlyStopCondition(stopIfTextOccurs(text)) |
| .build(); |
| |
| // Collect logs for up to 5 minutes |
| boolean isEarlyExit = logcatReceiver.collectLogs(/* timeoutMilliseconds= */ 5 * 60 * 1000); |
| |
| CLog.d("Finished collecting logs. Early exit = %s, collected logs = %s", isEarlyExit, |
| logcatReceiver.getCollectedLogs()); |
| |
| return logcatReceiver; |
| } |
| |
| private String makePattern(String text) { |
| return ".*" + text + ".*"; |
| } |
| |
| private void enableReceiver() { |
| TestDeviceHelper.enableComponent(mExtServicesPackageName, CLEANUP_RECEIVER_CLASS_NAME); |
| } |
| |
| private void logDeviceMetadata() { |
| String apexVersion = TestDeviceHelper.runShellCommand( |
| "pm list packages --apex-only --show-versioncode extservices").trim(); |
| String apkVersion = TestDeviceHelper.runShellCommand( |
| "pm list packages --show-versioncode ext.services").trim(); |
| String privAppName = TestDeviceHelper.runShellCommand( |
| "ls /apex/com.android.extservices/priv-app").trim(); |
| CLog.d("ExtServices apex version = <%s>, apk version = <%s>, priv-app = <%s>", apexVersion, |
| apkVersion, privAppName); |
| } |
| } |