• 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.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