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