1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.transparency.test; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.platform.test.annotations.LargeTest; 26 import android.platform.test.annotations.Presubmit; 27 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 32 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; 33 import com.android.tradefed.util.CommandResult; 34 import com.android.tradefed.util.CommandStatus; 35 36 import org.junit.Before; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 40 import java.util.concurrent.TimeUnit; 41 42 @Presubmit 43 @RunWith(DeviceJUnit4ClassRunner.class) 44 public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { 45 private static final String PACKAGE_NAME = "android.transparency.test.app"; 46 47 private static final String JOB_ID = "1740526926"; 48 49 /** Waiting time for the job to be scheduled */ 50 private static final int JOB_CREATION_MAX_SECONDS = 30; 51 52 @Before setUp()53 public void setUp() throws Exception { 54 cancelPendingJob(); 55 } 56 57 @Test testCollectAllApexInfo()58 public void testCollectAllApexInfo() throws Exception { 59 var options = new DeviceTestRunOptions(PACKAGE_NAME); 60 options.setTestClassName(PACKAGE_NAME + ".BinaryTransparencyTest"); 61 options.setTestMethodName("testCollectAllApexInfo"); 62 63 // Collect APEX package names from /apex, then pass them as expectation to be verified. 64 // The package names are collected from the find name with deduplication (NB: we used to 65 // deduplicate by dropping directory names with '@', but there's a DCLA case where it only 66 // has one directory with '@'. So we have to keep it and deduplicate the current way). 67 CommandResult result = getDevice().executeShellV2Command( 68 "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq"); 69 assertTrue(result.getStatus() == CommandStatus.SUCCESS); 70 String[] packageNames = result.getStdout().split("\n"); 71 for (var i = 0; i < packageNames.length; i++) { 72 options.addInstrumentationArg("apex-" + String.valueOf(i), packageNames[i]); 73 } 74 options.addInstrumentationArg("apex-number", Integer.toString(packageNames.length)); 75 runDeviceTests(options); 76 } 77 78 @Test testCollectAllUpdatedPreloadInfo()79 public void testCollectAllUpdatedPreloadInfo() throws Exception { 80 try { 81 updatePreloadApp(); 82 runDeviceTest("testCollectAllUpdatedPreloadInfo"); 83 } finally { 84 // No need to wait until job complete, since we can't verifying very meaningfully. 85 cancelPendingJob(); 86 uninstallPackage("com.android.egg"); 87 } 88 } 89 90 @Test testCollectAllSilentInstalledMbaInfo()91 public void testCollectAllSilentInstalledMbaInfo() throws Exception { 92 try { 93 new InstallMultiple() 94 .addFile("ApkVerityTestApp.apk") 95 .addFile("ApkVerityTestAppSplit.apk") 96 .run(); 97 updatePreloadApp(); 98 assertNotNull(getDevice().getAppPackageInfo("com.android.apkverity")); 99 assertNotNull(getDevice().getAppPackageInfo("com.android.egg")); 100 101 assertTrue(getDevice().setProperty("debug.transparency.bg-install-apps", 102 "com.android.apkverity,com.android.egg")); 103 runDeviceTest("testCollectAllSilentInstalledMbaInfo"); 104 } finally { 105 // No need to wait until job complete, since we can't verifying very meaningfully. 106 cancelPendingJob(); 107 uninstallPackage("com.android.apkverity"); 108 uninstallPackage("com.android.egg"); 109 } 110 } 111 112 @LargeTest 113 @Test testRebootlessApexUpdateTriggersJobScheduling()114 public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception { 115 try { 116 installRebootlessApex(); 117 118 // Verify 119 expectJobToBeScheduled(); 120 } finally { 121 // No need to wait until job complete, since we can't verifying very meaningfully. 122 uninstallRebootlessApexThenReboot(); 123 } 124 } 125 126 @Test testPreloadUpdateTriggersJobScheduling()127 public void testPreloadUpdateTriggersJobScheduling() throws Exception { 128 try { 129 updatePreloadApp(); 130 131 // Verify 132 expectJobToBeScheduled(); 133 } finally { 134 // No need to wait until job complete, since we can't verifying very meaningfully. 135 cancelPendingJob(); 136 uninstallPackage("com.android.egg"); 137 } 138 } 139 runDeviceTest(String method)140 private void runDeviceTest(String method) throws DeviceNotAvailableException { 141 var options = new DeviceTestRunOptions(PACKAGE_NAME); 142 options.setTestClassName(PACKAGE_NAME + ".BinaryTransparencyTest"); 143 options.setTestMethodName(method); 144 runDeviceTests(options); 145 } 146 cancelPendingJob()147 private void cancelPendingJob() throws DeviceNotAvailableException { 148 CommandResult result = getDevice().executeShellV2Command( 149 "cmd jobscheduler cancel android " + JOB_ID); 150 if (result.getStatus() == CommandStatus.SUCCESS) { 151 CLog.d("Canceling, output: " + result.getStdout()); 152 } else { 153 CLog.d("Something went wrong, error: " + result.getStderr()); 154 } 155 } 156 expectJobToBeScheduled()157 private void expectJobToBeScheduled() throws Exception { 158 for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) { 159 CommandResult result = getDevice().executeShellV2Command( 160 "cmd jobscheduler get-job-state android " + JOB_ID); 161 String state = result.getStdout().toString(); 162 CLog.i("Job status: " + state); 163 if (state.startsWith("unknown")) { 164 // The job hasn't been scheduled yet. So try again. 165 TimeUnit.SECONDS.sleep(1); 166 } else if (result.getExitCode() != 0) { 167 fail("Failing due to unexpected job state: " + result); 168 } else { 169 // The job exists, which is all we care about here 170 return; 171 } 172 } 173 fail("Timed out waiting for the job to be scheduled"); 174 } 175 installRebootlessApex()176 private void installRebootlessApex() throws Exception { 177 installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged"); 178 } 179 uninstallRebootlessApexThenReboot()180 private void uninstallRebootlessApexThenReboot() throws DeviceNotAvailableException { 181 // Reboot only if the APEX is not the pre-install one. 182 CommandResult result = getDevice().executeShellV2Command( 183 "pm list packages -f --apex-only |grep com.android.apex.cts.shim"); 184 assertTrue(result.getStatus() == CommandStatus.SUCCESS); 185 if (result.getStdout().contains("/data/apex/active/")) { 186 uninstallPackage("com.android.apex.cts.shim"); 187 getDevice().reboot(); 188 189 // Reboot enforces SELinux. Make it permissive again. 190 CommandResult runResult = getDevice().executeShellV2Command("setenforce 0"); 191 assertTrue(runResult.getStatus() == CommandStatus.SUCCESS); 192 } 193 } 194 updatePreloadApp()195 private void updatePreloadApp() throws DeviceNotAvailableException { 196 CommandResult result = getDevice().executeShellV2Command("pm path com.android.egg"); 197 assertTrue(result.getStatus() == CommandStatus.SUCCESS); 198 assertThat(result.getStdout()).startsWith("package:/system/app/"); 199 String path = result.getStdout().replaceFirst("^package:", ""); 200 201 result = getDevice().executeShellV2Command("pm install " + path); 202 assertTrue(result.getStatus() == CommandStatus.SUCCESS); 203 } 204 205 private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { InstallMultiple()206 InstallMultiple() { 207 super(getDevice(), getBuild()); 208 // Needed since in getMockBackgroundInstalledPackages, getPackageInfo runs as the caller 209 // uid. This also makes it consistent with installPackage's behavior. 210 addArg("--force-queryable"); 211 } 212 } 213 } 214