1 /*
2  * Copyright (C) 2018 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 android.server.am;
18 
19 import android.app.ActivityManager;
20 import android.app.KeyguardManager;
21 import android.content.Context;
22 import android.content.nano.DeviceConfigurationProto;
23 import android.content.nano.GlobalConfigurationProto;
24 import android.content.nano.LocaleProto;
25 import android.content.nano.ResourcesConfigurationProto;
26 import android.content.pm.ConfigurationInfo;
27 import android.content.pm.FeatureInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.SharedLibraryInfo;
30 import android.content.res.Configuration;
31 import android.graphics.Point;
32 import android.hardware.display.DisplayManager;
33 import android.opengl.GLES10;
34 import android.os.Build;
35 import android.os.LocaleList;
36 import android.os.ParcelFileDescriptor;
37 import android.support.test.InstrumentationRegistry;
38 import android.text.TextUtils;
39 import android.util.DisplayMetrics;
40 import android.view.Display;
41 
42 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
43 
44 import java.io.ByteArrayOutputStream;
45 import java.io.FileInputStream;
46 import java.io.IOException;
47 import java.nio.charset.StandardCharsets;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Set;
54 
55 import javax.microedition.khronos.egl.EGL10;
56 import javax.microedition.khronos.egl.EGLConfig;
57 import javax.microedition.khronos.egl.EGLContext;
58 import javax.microedition.khronos.egl.EGLDisplay;
59 import javax.microedition.khronos.egl.EGLSurface;
60 
61 import org.junit.Before;
62 import org.junit.Test;
63 
64 import static org.junit.Assert.assertArrayEquals;
65 import static org.junit.Assert.assertEquals;
66 
67 public class ActivityManagerGetConfigTests {
68     Context mContext;
69     ActivityManager mAm;
70     PackageManager mPm;
71 
72     @Before
setUp()73     public void setUp() throws Exception {
74         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
75         mAm = mContext.getSystemService(ActivityManager.class);
76         mPm = mContext.getPackageManager();
77     }
78 
executeShellCommand(String cmd)79     private byte[] executeShellCommand(String cmd) {
80         try {
81             ParcelFileDescriptor pfd =
82                     InstrumentationRegistry.getInstrumentation().getUiAutomation()
83                             .executeShellCommand(cmd);
84             byte[] buf = new byte[512];
85             int bytesRead;
86             FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
87             ByteArrayOutputStream stdout = new ByteArrayOutputStream();
88             while ((bytesRead = fis.read(buf)) != -1) {
89                 stdout.write(buf, 0, bytesRead);
90             }
91             fis.close();
92             return stdout.toByteArray();
93         } catch (IOException e) {
94             throw new RuntimeException(e);
95         }
96     }
97 
98     /**
99      * Adds all supported GL extensions for a provided EGLConfig to a set by creating an EGLContext
100      * and EGLSurface and querying extensions.
101      *
102      * @param egl An EGL API object
103      * @param display An EGLDisplay to create a context and surface with
104      * @param config The EGLConfig to get the extensions for
105      * @param surfaceSize eglCreatePbufferSurface generic parameters
106      * @param contextAttribs eglCreateContext generic parameters
107      * @param glExtensions A Set<String> to add GL extensions to
108      */
addExtensionsForConfig( EGL10 egl, EGLDisplay display, EGLConfig config, int[] surfaceSize, int[] contextAttribs, Set<String> glExtensions)109     private static void addExtensionsForConfig(
110             EGL10 egl,
111             EGLDisplay display,
112             EGLConfig config,
113             int[] surfaceSize,
114             int[] contextAttribs,
115             Set<String> glExtensions) {
116         // Create a context.
117         EGLContext context =
118                 egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttribs);
119         // No-op if we can't create a context.
120         if (context == EGL10.EGL_NO_CONTEXT) {
121             return;
122         }
123 
124         // Create a surface.
125         EGLSurface surface = egl.eglCreatePbufferSurface(display, config, surfaceSize);
126         if (surface == EGL10.EGL_NO_SURFACE) {
127             egl.eglDestroyContext(display, context);
128             return;
129         }
130 
131         // Update the current surface and context.
132         egl.eglMakeCurrent(display, surface, surface, context);
133 
134         // Get the list of extensions.
135         String extensionList = GLES10.glGetString(GLES10.GL_EXTENSIONS);
136         if (!TextUtils.isEmpty(extensionList)) {
137             // The list of extensions comes from the driver separated by spaces.
138             // Split them apart and add them into a Set for deduping purposes.
139             for (String extension : extensionList.split(" ")) {
140                 glExtensions.add(extension);
141             }
142         }
143 
144         // Tear down the context and surface for this config.
145         egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
146         egl.eglDestroySurface(display, surface);
147         egl.eglDestroyContext(display, context);
148     }
149 
150 
getGlExtensionsFromDriver()151     Set<String> getGlExtensionsFromDriver() {
152         Set<String> glExtensions = new HashSet<>();
153 
154         // Get the EGL implementation.
155         EGL10 egl = (EGL10) EGLContext.getEGL();
156         if (egl == null) {
157             throw new RuntimeException("Warning: couldn't get EGL");
158         }
159 
160         // Get the default display and initialize it.
161         EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
162         int[] version = new int[2];
163         egl.eglInitialize(display, version);
164 
165         // Call getConfigs() in order to find out how many there are.
166         int[] numConfigs = new int[1];
167         if (!egl.eglGetConfigs(display, null, 0, numConfigs)) {
168             throw new RuntimeException("Warning: couldn't get EGL config count");
169         }
170 
171         // Allocate space for all configs and ask again.
172         EGLConfig[] configs = new EGLConfig[numConfigs[0]];
173         if (!egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
174             throw new RuntimeException("Warning: couldn't get EGL configs");
175         }
176 
177         // Allocate surface size parameters outside of the main loop to cut down
178         // on GC thrashing.  1x1 is enough since we are only using it to get at
179         // the list of extensions.
180         int[] surfaceSize =
181                 new int[] {
182                         EGL10.EGL_WIDTH, 1,
183                         EGL10.EGL_HEIGHT, 1,
184                         EGL10.EGL_NONE
185                 };
186 
187         // For when we need to create a GLES2.0 context.
188         final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
189         int[] gles2 = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
190 
191         // For getting return values from eglGetConfigAttrib
192         int[] attrib = new int[1];
193 
194         for (int i = 0; i < numConfigs[0]; i++) {
195             // Get caveat for this config in order to skip slow (i.e. software) configs.
196             egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, attrib);
197             if (attrib[0] == EGL10.EGL_SLOW_CONFIG) {
198                 continue;
199             }
200 
201             // If the config does not support pbuffers we cannot do an eglMakeCurrent
202             // on it in addExtensionsForConfig(), so skip it here. Attempting to make
203             // it current with a pbuffer will result in an EGL_BAD_MATCH error
204             egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_SURFACE_TYPE, attrib);
205             if ((attrib[0] & EGL10.EGL_PBUFFER_BIT) == 0) {
206                 continue;
207             }
208 
209             final int EGL_OPENGL_ES_BIT = 0x0001;
210             final int EGL_OPENGL_ES2_BIT = 0x0004;
211             egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_RENDERABLE_TYPE, attrib);
212             if ((attrib[0] & EGL_OPENGL_ES_BIT) != 0) {
213                 addExtensionsForConfig(egl, display, configs[i], surfaceSize, null, glExtensions);
214             }
215             if ((attrib[0] & EGL_OPENGL_ES2_BIT) != 0) {
216                 addExtensionsForConfig(egl, display, configs[i], surfaceSize, gles2, glExtensions);
217             }
218         }
219 
220         // Release all EGL resources.
221         egl.eglTerminate(display);
222 
223         return glExtensions;
224     }
225 
checkResourceConfig(Configuration config, DisplayMetrics metrics, ResourcesConfigurationProto resConfig)226     private void checkResourceConfig(Configuration config, DisplayMetrics metrics,
227             ResourcesConfigurationProto resConfig) {
228         final int width, height;
229         if (metrics.widthPixels >= metrics.heightPixels) {
230             width = metrics.widthPixels;
231             height = metrics.heightPixels;
232         } else {
233             //noinspection SuspiciousNameCombination
234             width = metrics.heightPixels;
235             //noinspection SuspiciousNameCombination
236             height = metrics.widthPixels;
237         }
238 
239         assertEquals("Expected SDK version does not match",
240                 Build.VERSION.RESOURCES_SDK_INT, resConfig.sdkVersion);
241         assertEquals("Expected screen width px does not match",
242                 width, resConfig.screenWidthPx);
243         assertEquals("Expected screen width px does not match",
244                 height, resConfig.screenHeightPx);
245 
246         assertEquals("Expected font scale does not match",
247                 config.fontScale, resConfig.configuration.fontScale, Float.MIN_VALUE*5);
248         assertEquals("Expected mcc does not match",
249                 config.mcc, resConfig.configuration.mcc);
250         assertEquals("Expected mnc does not match",
251                 config.mnc, resConfig.configuration.mnc);
252         LocaleList llist = config.getLocales();
253         LocaleProto[] lprotos = resConfig.configuration.locales;
254         assertEquals("Expected number of locales does not match",
255                 llist.size(), lprotos.length);
256         for (int i = 0; i < llist.size(); i++) {
257             assertEquals("Expected locale #" + i + " language does not match",
258                     llist.get(i).getLanguage(), lprotos[i].language);
259             assertEquals("Expected locale #" + i + " country does not match",
260                     llist.get(i).getCountry(), lprotos[i].country);
261             assertEquals("Expected locale #" + i + " variant does not match",
262                     llist.get(i).getVariant(), lprotos[i].variant);
263         }
264         assertEquals("Expected screen layout does not match",
265                 config.screenLayout, resConfig.configuration.screenLayout);
266         assertEquals("Expected color mode does not match",
267                 config.colorMode, resConfig.configuration.colorMode);
268         assertEquals("Expected touchscreen does not match",
269                 config.touchscreen, resConfig.configuration.touchscreen);
270         assertEquals("Expected keyboard does not match",
271                 config.keyboard, resConfig.configuration.keyboard);
272         assertEquals("Expected keyboard hidden does not match",
273                 config.keyboardHidden, resConfig.configuration.keyboardHidden);
274         assertEquals("Expected hard keyboard hidden does not match",
275                 config.hardKeyboardHidden, resConfig.configuration.hardKeyboardHidden);
276         assertEquals("Expected navigation does not match",
277                 config.navigation, resConfig.configuration.navigation);
278         assertEquals("Expected navigation hidden does not match",
279                 config.navigationHidden, resConfig.configuration.navigationHidden);
280         assertEquals("Expected orientation does not match",
281                 config.orientation, resConfig.configuration.orientation);
282         assertEquals("Expected UI mode does not match",
283                 config.uiMode, resConfig.configuration.uiMode);
284         assertEquals("Expected screen width dp does not match",
285                 config.screenWidthDp, resConfig.configuration.screenWidthDp);
286         assertEquals("Expected screen hight dp does not match",
287                 config.screenHeightDp, resConfig.configuration.screenHeightDp);
288         assertEquals("Expected smallest screen width dp does not match",
289                 config.smallestScreenWidthDp, resConfig.configuration.smallestScreenWidthDp);
290         assertEquals("Expected density dpi does not match",
291                 config.densityDpi, resConfig.configuration.densityDpi);
292         // XXX not comparing windowConfiguration, since by definition this is contextual.
293     }
294 
checkDeviceConfig(DisplayManager dm, DeviceConfigurationProto deviceConfig)295     private void checkDeviceConfig(DisplayManager dm, DeviceConfigurationProto deviceConfig) {
296         Point stableSize = dm.getStableDisplaySize();
297         assertEquals("Expected stable screen width does not match",
298                 stableSize.x, deviceConfig.stableScreenWidthPx);
299         assertEquals("Expected stable screen height does not match",
300                 stableSize.y, deviceConfig.stableScreenHeightPx);
301         assertEquals("Expected stable screen density does not match",
302                 DisplayMetrics.DENSITY_DEVICE_STABLE, deviceConfig.stableDensityDpi);
303 
304         assertEquals("Expected total RAM does not match",
305                 mAm.getTotalRam(), deviceConfig.totalRam);
306         assertEquals("Expected low RAM does not match",
307                 mAm.isLowRamDevice(), deviceConfig.lowRam);
308         assertEquals("Expected max cores does not match",
309                 Runtime.getRuntime().availableProcessors(), deviceConfig.maxCores);
310         KeyguardManager kgm = mContext.getSystemService(KeyguardManager.class);
311         assertEquals("Expected has secure screen lock does not match",
312                 kgm.isDeviceSecure(), deviceConfig.hasSecureScreenLock);
313 
314         ConfigurationInfo configInfo = mAm.getDeviceConfigurationInfo();
315         if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) {
316             assertEquals("Expected opengl version does not match",
317                     configInfo.reqGlEsVersion, deviceConfig.openglVersion);
318         }
319 
320         Set<String> glExtensionsSet = getGlExtensionsFromDriver();
321         String[] glExtensions = new String[glExtensionsSet.size()];
322         glExtensions = glExtensionsSet.toArray(glExtensions);
323         Arrays.sort(glExtensions);
324         assertArrayEquals("Expected opengl extensions does not match",
325                 glExtensions, deviceConfig.openglExtensions);
326 
327         List<SharedLibraryInfo> slibs = mPm.getSharedLibraries(0);
328         Collections.sort(slibs, Comparator.comparing(SharedLibraryInfo::getName));
329         String[] slibNames = new String[slibs.size()];
330         for (int i = 0; i < slibs.size(); i++) {
331             slibNames[i] = slibs.get(i).getName();
332         }
333         assertArrayEquals("Expected shared libraries does not match",
334                 slibNames, deviceConfig.sharedLibraries);
335 
336         FeatureInfo[] features = mPm.getSystemAvailableFeatures();
337         Arrays.sort(features, (o1, o2) ->
338                 (o1.name == o2.name ? 0 : (o1.name == null ? -1 : o1.name.compareTo(o2.name))));
339         int size = 0;
340         for (int i = 0; i < features.length; i++) {
341             if (features[i].name != null) {
342                 size++;
343             }
344         }
345         String[] featureNames = new String[size];
346         for (int i = 0, j = 0; i < features.length; i++) {
347             if (features[i].name != null) {
348                 featureNames[j] = features[i].name;
349                 j++;
350             }
351         }
352         assertArrayEquals("Expected features does not match",
353                 featureNames, deviceConfig.features);
354     }
355 
356     @Test
testDeviceConfig()357     public void testDeviceConfig() {
358         byte[] dump = executeShellCommand("cmd activity get-config --proto --device");
359         GlobalConfigurationProto globalConfig;
360         try {
361             globalConfig = GlobalConfigurationProto.parseFrom(dump);
362         } catch (InvalidProtocolBufferNanoException ex) {
363             throw new RuntimeException("Failed to parse get-config:\n"
364                     + new String(dump, StandardCharsets.UTF_8), ex);
365         }
366 
367         Configuration config = mContext.getResources().getConfiguration();
368         DisplayManager dm = mContext.getSystemService(DisplayManager.class);
369         Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
370         DisplayMetrics metrics = new DisplayMetrics();
371         display.getMetrics(metrics);
372 
373         checkResourceConfig(config, metrics, globalConfig.resources);
374         checkDeviceConfig(dm, globalConfig.device);
375     }
376 }
377