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