1 /* 2 * Copyright (C) 2019 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.os.ext; 18 19 import static android.os.Build.VERSION_CODES; 20 import static android.os.Build.VERSION_CODES.BAKLAVA; 21 import static android.os.Build.VERSION_CODES.R; 22 import static android.os.Build.VERSION_CODES.S; 23 import static android.os.Build.VERSION_CODES.TIRAMISU; 24 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 25 import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM; 26 import static android.os.ext.SdkExtensions.AD_SERVICES; 27 28 import static com.android.os.ext.testing.CurrentVersion.CURRENT_TRAIN_VERSION; 29 import static com.android.os.ext.testing.CurrentVersion.R_BASE_VERSION; 30 import static com.android.os.ext.testing.CurrentVersion.S_BASE_VERSION; 31 import static com.android.os.ext.testing.CurrentVersion.T_BASE_VERSION; 32 33 import static com.google.common.truth.Truth.assertThat; 34 35 import static org.junit.Assert.assertEquals; 36 import static org.junit.Assert.assertThrows; 37 38 import android.app.ActivityManager; 39 import android.content.Context; 40 import android.content.pm.ModuleInfo; 41 import android.content.pm.PackageInfo; 42 import android.content.pm.PackageManager; 43 import android.os.SystemProperties; 44 import android.os.ext.SdkExtensions; 45 import android.util.Log; 46 47 import androidx.test.platform.app.InstrumentationRegistry; 48 import androidx.test.runner.AndroidJUnit4; 49 50 import com.android.modules.utils.build.SdkLevel; 51 import com.android.os.ext.testing.DeriveSdk; 52 53 import org.junit.BeforeClass; 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 57 import java.util.HashSet; 58 import java.util.Set; 59 60 @RunWith(AndroidJUnit4.class) 61 public class SdkExtensionsTest { 62 63 private static final String TAG = "SdkExtensionsTest"; 64 65 private enum Expectation { 66 /** 67 * Expect an extension to be the current / latest defined version, or later (which may be 68 * the case if the device under test comes from a more recent build that the tests come 69 * from). 70 */ 71 AT_LEAST_CURRENT, 72 /** Expect an extension to be missing / version 0 */ 73 MISSING, 74 /** Expect an extension to be at least the base extension version of the device */ 75 AT_LEAST_BASE, 76 } 77 78 private static final Expectation AT_LEAST_CURRENT = Expectation.AT_LEAST_CURRENT; 79 private static final Expectation MISSING = Expectation.MISSING; 80 private static final Expectation AT_LEAST_BASE = Expectation.AT_LEAST_BASE; 81 assertAtLeastBaseVersion(int version)82 private static void assertAtLeastBaseVersion(int version) { 83 int minVersion = R_BASE_VERSION; 84 if (SdkLevel.isAtLeastU()) { 85 minVersion = CURRENT_TRAIN_VERSION; 86 } else if (SdkLevel.isAtLeastT()) { 87 minVersion = T_BASE_VERSION; 88 } else if (SdkLevel.isAtLeastS()) { 89 minVersion = S_BASE_VERSION; 90 } 91 assertThat(version).isAtLeast(minVersion); 92 assertThat(version).isAtMost(CURRENT_TRAIN_VERSION); 93 } 94 assertVersion(Expectation expectation, int version)95 private static void assertVersion(Expectation expectation, int version) { 96 switch (expectation) { 97 case AT_LEAST_CURRENT: 98 assertThat(version).isAtLeast(CURRENT_TRAIN_VERSION); 99 break; 100 case AT_LEAST_BASE: 101 assertAtLeastBaseVersion(version); 102 break; 103 case MISSING: 104 assertEquals(0, version); 105 break; 106 } 107 } 108 assertVersion(Expectation expectation, String propValue)109 private static void assertVersion(Expectation expectation, String propValue) { 110 if (expectation == Expectation.MISSING) { 111 assertEquals("", propValue); 112 } else { 113 int version = Integer.parseInt(propValue); 114 assertVersion(expectation, version); 115 } 116 } 117 assertVersion(Expectation expectation, int extension, String propId)118 public static final void assertVersion(Expectation expectation, int extension, String propId) { 119 String prop = "build.version.extensions." + propId; 120 assertVersion(expectation, SystemProperties.get(prop)); 121 assertVersion(expectation, SdkExtensions.getExtensionVersion(extension)); 122 if (expectation != Expectation.MISSING) { 123 int v = SdkExtensions.getAllExtensionVersions().get(extension); 124 assertVersion(expectation, v); 125 } 126 } 127 128 /* This method runs the copy of the dump code that is bundled inside the test APK. */ 129 @BeforeClass runTestDeriveSdkDump()130 public static void runTestDeriveSdkDump() { 131 Log.i(TAG, "derive_sdk dump (bundled with test):"); 132 133 for (String line : DeriveSdk.dump()) { 134 Log.i(TAG, " " + line); 135 } 136 } 137 138 /** Verify that getExtensionVersion only accepts valid extension SDKs */ 139 @Test testBadArgument()140 public void testBadArgument() throws Exception { 141 // R is the first SDK version with extensions. Ideally, we'd test all <R values, 142 // but it would take too long, so take 10k samples. 143 int step = (int) ((VERSION_CODES.R - (long) Integer.MIN_VALUE) / 10_000); 144 for (int sdk = Integer.MIN_VALUE; sdk < VERSION_CODES.R; sdk += step) { 145 final int finalSdk = sdk; 146 assertThrows( 147 IllegalArgumentException.class, 148 () -> SdkExtensions.getExtensionVersion(finalSdk)); 149 } 150 } 151 152 /** Verifies that getExtensionVersion returns zero value for non-existing extensions */ 153 @Test testZeroValues()154 public void testZeroValues() throws Exception { 155 Set<Integer> assignedCodes = 156 Set.of(R, S, TIRAMISU, UPSIDE_DOWN_CAKE, VANILLA_ICE_CREAM, BAKLAVA, AD_SERVICES); 157 for (int sdk = VERSION_CODES.R; sdk <= 1_000_000; sdk++) { 158 if (assignedCodes.contains(sdk)) { 159 continue; 160 } 161 // No extension SDKs yet. 162 int version = SdkExtensions.getExtensionVersion(sdk); 163 assertEquals("Extension ID " + sdk + " has non-zero version", 0, version); 164 } 165 } 166 167 @Test testGetAllExtensionVersionsKeys()168 public void testGetAllExtensionVersionsKeys() throws Exception { 169 Set<Integer> expectedKeys = new HashSet<>(); 170 expectedKeys.add(VERSION_CODES.R); 171 if (SdkLevel.isAtLeastS()) { 172 expectedKeys.add(VERSION_CODES.S); 173 } 174 if (SdkLevel.isAtLeastT()) { 175 expectedKeys.add(VERSION_CODES.TIRAMISU); 176 expectedKeys.add(AD_SERVICES); 177 } 178 if (SdkLevel.isAtLeastU()) { 179 expectedKeys.add(UPSIDE_DOWN_CAKE); 180 } 181 if (SdkLevel.isAtLeastV()) { 182 expectedKeys.add(VANILLA_ICE_CREAM); 183 } 184 if (SdkLevel.isAtLeastB()) { 185 expectedKeys.add(BAKLAVA); 186 } 187 Set<Integer> actualKeys = SdkExtensions.getAllExtensionVersions().keySet(); 188 assertThat(actualKeys).containsExactlyElementsIn(expectedKeys); 189 } 190 191 @Test testExtensionR()192 public void testExtensionR() throws Exception { 193 Expectation expectation = dessertExpectation(true); 194 assertVersion(expectation, R, "r"); 195 } 196 197 @Test testExtensionS()198 public void testExtensionS() throws Exception { 199 Expectation expectation = dessertExpectation(SdkLevel.isAtLeastS()); 200 assertVersion(expectation, S, "s"); 201 } 202 203 @Test testExtensionT()204 public void testExtensionT() throws Exception { 205 Expectation expectation = dessertExpectation(SdkLevel.isAtLeastT()); 206 assertVersion(expectation, TIRAMISU, "t"); 207 } 208 209 @Test testExtensionU()210 public void testExtensionU() throws Exception { 211 Expectation expectation = dessertExpectation(SdkLevel.isAtLeastU()); 212 assertVersion(expectation, UPSIDE_DOWN_CAKE, "u"); 213 } 214 215 @Test testExtensionV()216 public void testExtensionV() throws Exception { 217 Expectation expectation = dessertExpectation(SdkLevel.isAtLeastV()); 218 assertVersion(expectation, VANILLA_ICE_CREAM, "v"); 219 } 220 221 @Test testExtensionB()222 public void testExtensionB() throws Exception { 223 Expectation expectation = dessertExpectation(SdkLevel.isAtLeastB()); 224 assertVersion(expectation, BAKLAVA, "b"); 225 } 226 227 @Test testExtensionAdServices()228 public void testExtensionAdServices() throws Exception { 229 // Go trains do not ship the latest versions of AdServices, though they should. Temporarily 230 // accept AT_LEAST_BASE of AdServices until the Go train situation has been resolved, then 231 // revert back to expecting MISSING (before T) or CURRENT (on T+). 232 Expectation expectation = dessertExpectation(SdkLevel.isAtLeastT()); 233 assertVersion(expectation, AD_SERVICES, "ad_services"); 234 } 235 dessertExpectation(boolean expectedPresent)236 private Expectation dessertExpectation(boolean expectedPresent) throws Exception { 237 if (!expectedPresent) { 238 return MISSING; 239 } 240 // Go trains don't include all modules, so even when all trains for a particular release 241 // have been installed correctly on a Go device, we can't generally expect the extension 242 // version to be the current train version. 243 return SdkLevel.isAtLeastT() && isGoWithSideloadedModules() 244 ? AT_LEAST_BASE 245 : AT_LEAST_CURRENT; 246 } 247 isGoWithSideloadedModules()248 private boolean isGoWithSideloadedModules() throws Exception { 249 Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 250 boolean isGoDevice = context.getSystemService(ActivityManager.class).isLowRamDevice(); 251 if (!isGoDevice) { 252 return false; 253 } 254 255 PackageManager packageManager = context.getPackageManager(); 256 boolean anyApexesSideloaded = false; 257 for (ModuleInfo module : packageManager.getInstalledModules(0)) { 258 boolean sideloaded = isSideloadedApex(packageManager, module.getPackageName()); 259 anyApexesSideloaded |= sideloaded; 260 } 261 return anyApexesSideloaded; 262 } 263 isSideloadedApex(PackageManager packageManager, String packageName)264 private static boolean isSideloadedApex(PackageManager packageManager, String packageName) 265 throws Exception { 266 int flags = PackageManager.MATCH_APEX; 267 PackageInfo currentInfo = packageManager.getPackageInfo(packageName, flags); 268 if (!currentInfo.isApex) { 269 return false; 270 } 271 flags |= PackageManager.MATCH_FACTORY_ONLY; 272 PackageInfo factoryInfo = packageManager.getPackageInfo(packageName, flags); 273 return !factoryInfo.applicationInfo.sourceDir.equals(currentInfo.applicationInfo.sourceDir); 274 } 275 } 276