/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.server.wm; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.app.ActivityManager; import android.app.KeyguardManager; import android.content.Context; import android.content.nano.DeviceConfigurationProto; import android.content.nano.GlobalConfigurationProto; import android.content.nano.LocaleProto; import android.content.nano.ResourcesConfigurationProto; import android.content.pm.ConfigurationInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.opengl.GLES10; import android.os.Build; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.Display; import com.google.protobuf.nano.InvalidProtocolBufferNanoException; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; @Presubmit public class ActivityManagerGetConfigTests { Context mContext; ActivityManager mAm; PackageManager mPm; @Before public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); mAm = mContext.getSystemService(ActivityManager.class); mPm = mContext.getPackageManager(); } private byte[] executeShellCommand(String cmd) { try { ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation() .executeShellCommand(cmd); byte[] buf = new byte[512]; int bytesRead; FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); ByteArrayOutputStream stdout = new ByteArrayOutputStream(); while ((bytesRead = fis.read(buf)) != -1) { stdout.write(buf, 0, bytesRead); } fis.close(); return stdout.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } /** * Adds all supported GL extensions for a provided EGLConfig to a set by creating an EGLContext * and EGLSurface and querying extensions. * * @param egl An EGL API object * @param display An EGLDisplay to create a context and surface with * @param config The EGLConfig to get the extensions for * @param surfaceSize eglCreatePbufferSurface generic parameters * @param contextAttribs eglCreateContext generic parameters * @param glExtensions A Set to add GL extensions to */ private static void addExtensionsForConfig( EGL10 egl, EGLDisplay display, EGLConfig config, int[] surfaceSize, int[] contextAttribs, Set glExtensions) { // Create a context. EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttribs); // No-op if we can't create a context. if (context == EGL10.EGL_NO_CONTEXT) { return; } // Create a surface. EGLSurface surface = egl.eglCreatePbufferSurface(display, config, surfaceSize); if (surface == EGL10.EGL_NO_SURFACE) { egl.eglDestroyContext(display, context); return; } // Update the current surface and context. egl.eglMakeCurrent(display, surface, surface, context); // Get the list of extensions. String extensionList = GLES10.glGetString(GLES10.GL_EXTENSIONS); if (!TextUtils.isEmpty(extensionList)) { // The list of extensions comes from the driver separated by spaces. // Split them apart and add them into a Set for deduping purposes. for (String extension : extensionList.split(" ")) { glExtensions.add(extension); } } // Tear down the context and surface for this config. egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); egl.eglDestroySurface(display, surface); egl.eglDestroyContext(display, context); } Set getGlExtensionsFromDriver() { Set glExtensions = new HashSet<>(); // Get the EGL implementation. EGL10 egl = (EGL10) EGLContext.getEGL(); if (egl == null) { throw new RuntimeException("Warning: couldn't get EGL"); } // Get the default display and initialize it. EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int[2]; egl.eglInitialize(display, version); // Call getConfigs() in order to find out how many there are. int[] numConfigs = new int[1]; if (!egl.eglGetConfigs(display, null, 0, numConfigs)) { throw new RuntimeException("Warning: couldn't get EGL config count"); } // Allocate space for all configs and ask again. EGLConfig[] configs = new EGLConfig[numConfigs[0]]; if (!egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) { throw new RuntimeException("Warning: couldn't get EGL configs"); } // Allocate surface size parameters outside of the main loop to cut down // on GC thrashing. 1x1 is enough since we are only using it to get at // the list of extensions. int[] surfaceSize = new int[] { EGL10.EGL_WIDTH, 1, EGL10.EGL_HEIGHT, 1, EGL10.EGL_NONE }; // For when we need to create a GLES2.0 context. final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; int[] gles2 = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; // For getting return values from eglGetConfigAttrib int[] attrib = new int[1]; for (int i = 0; i < numConfigs[0]; i++) { // Get caveat for this config in order to skip slow (i.e. software) configs. egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, attrib); if (attrib[0] == EGL10.EGL_SLOW_CONFIG) { continue; } // If the config does not support pbuffers we cannot do an eglMakeCurrent // on it in addExtensionsForConfig(), so skip it here. Attempting to make // it current with a pbuffer will result in an EGL_BAD_MATCH error egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_SURFACE_TYPE, attrib); if ((attrib[0] & EGL10.EGL_PBUFFER_BIT) == 0) { continue; } final int EGL_OPENGL_ES_BIT = 0x0001; final int EGL_OPENGL_ES2_BIT = 0x0004; egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_RENDERABLE_TYPE, attrib); if ((attrib[0] & EGL_OPENGL_ES_BIT) != 0) { addExtensionsForConfig(egl, display, configs[i], surfaceSize, null, glExtensions); } if ((attrib[0] & EGL_OPENGL_ES2_BIT) != 0) { addExtensionsForConfig(egl, display, configs[i], surfaceSize, gles2, glExtensions); } } // Release all EGL resources. egl.eglTerminate(display); return glExtensions; } private void checkResourceConfig(Configuration config, DisplayMetrics metrics, ResourcesConfigurationProto resConfig) { final int width, height; if (metrics.widthPixels >= metrics.heightPixels) { width = metrics.widthPixels; height = metrics.heightPixels; } else { //noinspection SuspiciousNameCombination width = metrics.heightPixels; //noinspection SuspiciousNameCombination height = metrics.widthPixels; } assertEquals("Expected SDK version does not match", Build.VERSION.RESOURCES_SDK_INT, resConfig.sdkVersion); assertEquals("Expected screen width px does not match", width, resConfig.screenWidthPx); assertEquals("Expected screen width px does not match", height, resConfig.screenHeightPx); assertEquals("Expected font scale does not match", config.fontScale, resConfig.configuration.fontScale, Float.MIN_VALUE*5); assertEquals("Expected mcc does not match", config.mcc, resConfig.configuration.mcc); assertEquals("Expected mnc does not match", config.mnc, resConfig.configuration.mnc); LocaleList llist = config.getLocales(); LocaleList lprotos = LocaleList.forLanguageTags(resConfig.configuration.localeList); assertEquals("Expected number of locales does not match", llist.size(), lprotos.size()); for (int i = 0; i < llist.size(); i++) { assertEquals("Expected locale #" + i + " does not match", llist.get(i).toLanguageTag(), lprotos.get(i).toLanguageTag()); } assertEquals("Expected screen layout does not match", config.screenLayout, resConfig.configuration.screenLayout); assertEquals("Expected color mode does not match", config.colorMode, resConfig.configuration.colorMode); assertEquals("Expected touchscreen does not match", config.touchscreen, resConfig.configuration.touchscreen); assertEquals("Expected keyboard does not match", config.keyboard, resConfig.configuration.keyboard); assertEquals("Expected keyboard hidden does not match", config.keyboardHidden, resConfig.configuration.keyboardHidden); assertEquals("Expected hard keyboard hidden does not match", config.hardKeyboardHidden, resConfig.configuration.hardKeyboardHidden); assertEquals("Expected navigation does not match", config.navigation, resConfig.configuration.navigation); assertEquals("Expected navigation hidden does not match", config.navigationHidden, resConfig.configuration.navigationHidden); assertEquals("Expected orientation does not match", config.orientation, resConfig.configuration.orientation); assertEquals("Expected UI mode does not match", config.uiMode, resConfig.configuration.uiMode); assertEquals("Expected screen width dp does not match", config.screenWidthDp, resConfig.configuration.screenWidthDp); assertEquals("Expected screen hight dp does not match", config.screenHeightDp, resConfig.configuration.screenHeightDp); assertEquals("Expected smallest screen width dp does not match", config.smallestScreenWidthDp, resConfig.configuration.smallestScreenWidthDp); assertEquals("Expected density dpi does not match", config.densityDpi, resConfig.configuration.densityDpi); // XXX not comparing windowConfiguration, since by definition this is contextual. } private void checkDeviceConfig(DisplayMetrics displayMetrics, DeviceConfigurationProto deviceConfig) { assertEquals("Expected stable screen width does not match", displayMetrics.widthPixels, deviceConfig.stableScreenWidthPx); assertEquals("Expected stable screen height does not match", displayMetrics.heightPixels, deviceConfig.stableScreenHeightPx); assertEquals("Expected stable screen density does not match", DisplayMetrics.DENSITY_DEVICE_STABLE, deviceConfig.stableDensityDpi); assertEquals("Expected total RAM does not match", mAm.getTotalRam(), deviceConfig.totalRam); assertEquals("Expected low RAM does not match", mAm.isLowRamDevice(), deviceConfig.lowRam); assertEquals("Expected max cores does not match", Runtime.getRuntime().availableProcessors(), deviceConfig.maxCores); KeyguardManager kgm = mContext.getSystemService(KeyguardManager.class); assertEquals("Expected has secure screen lock does not match", kgm.isDeviceSecure(), deviceConfig.hasSecureScreenLock); ConfigurationInfo configInfo = mAm.getDeviceConfigurationInfo(); if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) { assertEquals("Expected opengl version does not match", configInfo.reqGlEsVersion, deviceConfig.openglVersion); } Set glExtensionsSet = getGlExtensionsFromDriver(); String[] glExtensions = new String[glExtensionsSet.size()]; glExtensions = glExtensionsSet.toArray(glExtensions); Arrays.sort(glExtensions); assertArrayEquals("Expected opengl extensions does not match", glExtensions, deviceConfig.openglExtensions); List slibs = mPm.getSharedLibraries(0); Collections.sort(slibs, Comparator.comparing(SharedLibraryInfo::getName)); String[] slibNames = new String[slibs.size()]; for (int i = 0; i < slibs.size(); i++) { slibNames[i] = slibs.get(i).getName(); } assertArrayEquals("Expected shared libraries does not match", slibNames, deviceConfig.sharedLibraries); FeatureInfo[] features = mPm.getSystemAvailableFeatures(); Arrays.sort(features, (o1, o2) -> { if (o1.name == o2.name) return 0; if (o1.name == null) return -1; if (o2.name == null) return 1; return o1.name.compareTo(o2.name); }); int size = 0; for (int i = 0; i < features.length; i++) { if (features[i].name != null) { size++; } } String[] featureNames = new String[size]; for (int i = 0, j = 0; i < features.length; i++) { if (features[i].name != null) { featureNames[j] = features[i].name; j++; } } assertArrayEquals("Expected features does not match", featureNames, deviceConfig.features); } @Test public void testDeviceConfig() { byte[] dump = executeShellCommand("cmd activity get-config --proto --device"); GlobalConfigurationProto globalConfig; try { globalConfig = GlobalConfigurationProto.parseFrom(dump); } catch (InvalidProtocolBufferNanoException ex) { throw new RuntimeException("Failed to parse get-config:\n" + new String(dump, StandardCharsets.UTF_8), ex); } Configuration config = mContext.getResources().getConfiguration(); DisplayManager dm = mContext.getSystemService(DisplayManager.class); Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); checkResourceConfig(config, metrics, globalConfig.resources); checkDeviceConfig(metrics, globalConfig.device); } @Test public void testDeviceConfigWithSecondaryDisplay() throws Exception { VirtualDisplayHelper vd = new VirtualDisplayHelper(); final int displayId = vd.setPublicDisplay(true).createAndWaitForDisplay(); DisplayManager dm = mContext.getSystemService(DisplayManager.class); Display display = dm.getDisplay(displayId); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); String cmd = "cmd activity get-config --proto --device --display " + displayId; byte[] dump = executeShellCommand(cmd); GlobalConfigurationProto globalConfig; globalConfig = GlobalConfigurationProto.parseFrom(dump); Configuration config = mContext.getResources().getConfiguration(); checkResourceConfig(config, metrics, globalConfig.resources); checkDeviceConfig(metrics, globalConfig.device); vd.releaseDisplay(); } }