• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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