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