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