1 /* 2 * Copyright (C) 2021 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 com.android.sdkext.extensions; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assume.assumeTrue; 26 import org.junit.Ignore; 27 28 import android.cts.install.lib.host.InstallUtilsHost; 29 30 import com.android.os.ext.testing.CurrentVersion; 31 import com.android.tests.rollback.host.AbandonSessionsRule; 32 import com.android.tradefed.device.ITestDevice.ApexInfo; 33 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 34 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 35 import com.android.tradefed.util.CommandResult; 36 37 import org.junit.After; 38 import org.junit.Before; 39 import org.junit.Rule; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 import java.io.File; 44 import java.lang.NumberFormatException; 45 import java.time.Duration; 46 import java.util.regex.Matcher; 47 import java.util.regex.Pattern; 48 49 @RunWith(DeviceJUnit4ClassRunner.class) 50 public class SdkExtensionsHostTest extends BaseHostJUnit4Test { 51 52 private static final String APP_FILENAME = "sdkextensions_e2e_test_app.apk"; 53 private static final String APP_PACKAGE = "com.android.sdkext.extensions.apps"; 54 private static final String APP_R12_FILENAME = "sdkextensions_e2e_test_app_req_r12.apk"; 55 private static final String APP_R12_PACKAGE = "com.android.sdkext.extensions.apps.r12"; 56 private static final String APP_S12_FILENAME = "sdkextensions_e2e_test_app_req_s12.apk"; 57 private static final String APP_S12_PACKAGE = "com.android.sdkext.extensions.apps.s12"; 58 private static final String APP_R45_FILENAME = "sdkextensions_e2e_test_app_req_r45.apk"; 59 private static final String APP_R45_PACKAGE = "com.android.sdkext.extensions.apps.r45"; 60 private static final String APP_S45_FILENAME = "sdkextensions_e2e_test_app_req_s45.apk"; 61 private static final String APP_S45_PACKAGE = "com.android.sdkext.extensions.apps.s45"; 62 private static final String MEDIA_FILENAME = "test_com.android.media.apex"; 63 private static final String SDKEXTENSIONS_FILENAME = "test_com.android.sdkext.apex"; 64 65 private static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2); 66 67 private final InstallUtilsHost mInstallUtils = new InstallUtilsHost(this); 68 69 private Boolean mIsAtLeastS = null; 70 private Boolean mIsAtLeastT = null; 71 72 @Rule 73 public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); 74 75 @Before setUp()76 public void setUp() throws Exception { 77 assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported()); 78 } 79 80 @Before installTestApp()81 public void installTestApp() throws Exception { 82 File testAppFile = mInstallUtils.getTestFile(APP_FILENAME); 83 String installResult = getDevice().installPackage(testAppFile, true); 84 assertNull(installResult); 85 } 86 87 @Before // Generally not needed, but local test devices are sometimes in a "bad" start state. 88 @After cleanup()89 public void cleanup() throws Exception { 90 getDevice().uninstallPackage(APP_PACKAGE); 91 uninstallApexes(SDKEXTENSIONS_FILENAME, MEDIA_FILENAME); 92 } 93 94 @Test 95 @Ignore("b/274764792") testDefault()96 public void testDefault() throws Exception { 97 assertVersionDefault(); 98 } 99 100 @Test 101 @Ignore("b/274764792") upgradeOneApexWithBump()102 public void upgradeOneApexWithBump() throws Exception { 103 assertVersionDefault(); 104 mInstallUtils.installApexes(SDKEXTENSIONS_FILENAME); 105 reboot(); 106 107 // Version 12 requires sdkext, which is fulfilled 108 // Version 45 requires sdkext + media, which isn't fulfilled 109 assertRVersionEquals(12); 110 assertSVersionEquals(12); 111 assertTestMethodsPresent(); // 45 APIs are available on 12 too. 112 } 113 114 @Test 115 @Ignore("b/274764792") upgradeOneApex()116 public void upgradeOneApex() throws Exception { 117 // Version 45 requires updated sdkext and media, so updating just media changes nothing. 118 assertVersionDefault(); 119 mInstallUtils.installApexes(MEDIA_FILENAME); 120 reboot(); 121 assertVersionDefault(); 122 } 123 124 @Test 125 @Ignore("b/274764792") upgradeTwoApexes()126 public void upgradeTwoApexes() throws Exception { 127 // Updating sdkext and media bumps the version to 45. 128 assertVersionDefault(); 129 mInstallUtils.installApexes(MEDIA_FILENAME, SDKEXTENSIONS_FILENAME); 130 reboot(); 131 assertVersion45(); 132 } 133 canInstallApp(String filename, String packageName)134 private boolean canInstallApp(String filename, String packageName) throws Exception { 135 File appFile = mInstallUtils.getTestFile(filename); 136 String installResult = getDevice().installPackage(appFile, true); 137 if (installResult != null) { 138 return false; 139 } 140 assertNull(getDevice().uninstallPackage(packageName)); 141 return true; 142 } 143 getExtensionVersionFromSysprop(String v)144 private String getExtensionVersionFromSysprop(String v) throws Exception { 145 String command = "getprop build.version.extensions." + v; 146 CommandResult res = getDevice().executeShellV2Command(command); 147 assertEquals(0, (int) res.getExitCode()); 148 return res.getStdout().replace("\n", ""); 149 } 150 broadcast(String action, String extra)151 private String broadcast(String action, String extra) throws Exception { 152 String command = getBroadcastCommand(action, extra); 153 CommandResult res = getDevice().executeShellV2Command(command); 154 assertEquals(0, (int) res.getExitCode()); 155 Matcher matcher = Pattern.compile("data=\"([^\"]+)\"").matcher(res.getStdout()); 156 assertTrue("Unexpected output from am broadcast: " + res.getStdout(), matcher.find()); 157 return matcher.group(1); 158 } 159 broadcastForBoolean(String action, String extra)160 private boolean broadcastForBoolean(String action, String extra) throws Exception { 161 String result = broadcast(action, extra); 162 if (result.equals("true") || result.equals("false")) { 163 return result.equals("true"); 164 } 165 throw getAppParsingError(result); 166 } 167 broadcastForInt(String action, String extra)168 private int broadcastForInt(String action, String extra) throws Exception { 169 String result = broadcast(action, extra); 170 try { 171 return Integer.parseInt(result); 172 } catch (NumberFormatException e) { 173 throw getAppParsingError(result); 174 } 175 } 176 getAppParsingError(String result)177 private Error getAppParsingError(String result) { 178 String message = "App error! Full stack trace in logcat (grep for SdkExtensionsE2E): "; 179 return new AssertionError(message + result); 180 } 181 assertVersionDefault()182 private void assertVersionDefault() throws Exception { 183 int expected = isAtLeastT() ? CurrentVersion.T_BASE_VERSION 184 : isAtLeastS() ? CurrentVersion.S_BASE_VERSION 185 : CurrentVersion.R_BASE_VERSION; 186 assertRVersionEquals(expected); 187 assertSVersionEquals(expected); 188 assertTestMethodsNotPresent(); 189 } 190 assertVersion45()191 private void assertVersion45() throws Exception { 192 assertRVersionEquals(45); 193 assertSVersionEquals(45); 194 assertTestMethodsPresent(); 195 } 196 assertTestMethodsNotPresent()197 private void assertTestMethodsNotPresent() throws Exception { 198 assertTrue(broadcastForBoolean("MAKE_CALLS_DEFAULT", null)); 199 } 200 assertTestMethodsPresent()201 private void assertTestMethodsPresent() throws Exception { 202 if (isAtLeastS()) { 203 assertTrue(broadcastForBoolean("MAKE_CALLS_45", null)); 204 } else { 205 // The APIs in the test apex are not currently getting installed correctly 206 // on Android R devices because they rely on the dynamic classpath feature. 207 // TODO(b/234361913): fix this 208 assertTestMethodsNotPresent(); 209 } 210 } 211 assertRVersionEquals(int version)212 private void assertRVersionEquals(int version) throws Exception { 213 int appValue = broadcastForInt("GET_SDK_VERSION", "r"); 214 String syspropValue = getExtensionVersionFromSysprop("r"); 215 assertEquals(version, appValue); 216 assertEquals(String.valueOf(version), syspropValue); 217 assertEquals(version >= 12, canInstallApp(APP_R12_FILENAME, APP_R12_PACKAGE)); 218 assertEquals(version >= 45, canInstallApp(APP_R45_FILENAME, APP_R45_PACKAGE)); 219 } 220 assertSVersionEquals(int version)221 private void assertSVersionEquals(int version) throws Exception { 222 int appValue = broadcastForInt("GET_SDK_VERSION", "s"); 223 String syspropValue = getExtensionVersionFromSysprop("s"); 224 if (isAtLeastS()) { 225 assertEquals(version, appValue); 226 assertEquals(String.valueOf(version), syspropValue); 227 228 // These APKs require the same R version as they do S version. 229 int minVersion = Math.min(version, broadcastForInt("GET_SDK_VERSION", "r")); 230 assertEquals(minVersion >= 12, canInstallApp(APP_S12_FILENAME, APP_S12_PACKAGE)); 231 assertEquals(minVersion >= 45, canInstallApp(APP_S45_FILENAME, APP_S45_PACKAGE)); 232 } else { 233 assertEquals(0, appValue); 234 assertEquals("", syspropValue); 235 assertFalse(canInstallApp(APP_S12_FILENAME, APP_S12_PACKAGE)); 236 assertFalse(canInstallApp(APP_S45_FILENAME, APP_S45_PACKAGE)); 237 } 238 } 239 getBroadcastCommand(String action, String extra)240 private static String getBroadcastCommand(String action, String extra) { 241 String cmd = "am broadcast"; 242 cmd += " -a com.android.sdkext.extensions.apps." + action; 243 if (extra != null) { 244 cmd += " -e extra " + extra; 245 } 246 cmd += " -n com.android.sdkext.extensions.apps/.Receiver"; 247 return cmd; 248 } 249 isAtLeastS()250 private boolean isAtLeastS() throws Exception { 251 if (mIsAtLeastS == null) { 252 mIsAtLeastS = broadcastForBoolean("IS_AT_LEAST", "s"); 253 } 254 return mIsAtLeastS; 255 } 256 isAtLeastT()257 private boolean isAtLeastT() throws Exception { 258 if (mIsAtLeastT == null) { 259 mIsAtLeastT = broadcastForBoolean("IS_AT_LEAST", "t"); 260 } 261 return mIsAtLeastT; 262 } 263 uninstallApexes(String... filenames)264 private boolean uninstallApexes(String... filenames) throws Exception { 265 boolean reboot = false; 266 for (String filename : filenames) { 267 ApexInfo apex = mInstallUtils.getApexInfo(mInstallUtils.getTestFile(filename)); 268 String res = getDevice().uninstallPackage(apex.name); 269 // res is null for successful uninstalls (non-null likely implesfactory version). 270 reboot |= res == null; 271 } 272 if (reboot) { 273 reboot(); 274 return true; 275 } 276 return false; 277 } 278 reboot()279 private void reboot() throws Exception { 280 getDevice().reboot(); 281 boolean success = getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis()); 282 assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue(); 283 } 284 } 285