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