1 /* 2 * Copyright (C) 2015 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 package com.android.compatibility.common.deviceinfo; 17 18 import android.app.Activity; 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.hardware.devicestate.DeviceState; 22 import android.hardware.devicestate.DeviceStateManager; 23 import android.os.Build; 24 import android.os.Bundle; 25 import android.server.wm.jetpack.extensions.util.ExtensionsUtil; 26 import android.server.wm.jetpack.extensions.util.SidecarUtil; 27 import android.server.wm.jetpack.extensions.util.Version; 28 import android.util.DisplayMetrics; 29 import android.view.Display; 30 import android.view.WindowManager; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.annotation.RequiresApi; 35 36 import com.android.compatibility.common.util.ApiLevelUtil; 37 import com.android.compatibility.common.util.DeviceInfoStore; 38 import com.android.compatibility.common.util.DummyActivity; 39 40 import java.io.IOException; 41 import java.lang.reflect.Method; 42 import java.util.Arrays; 43 import java.util.Comparator; 44 import java.util.List; 45 import java.util.Optional; 46 47 /** 48 * Screen device info collector. 49 */ 50 public final class ScreenDeviceInfo extends DeviceInfo { 51 52 @Override collectDeviceInfo(DeviceInfoStore store)53 protected void collectDeviceInfo(DeviceInfoStore store) throws Exception { 54 DisplayMetrics metrics = new DisplayMetrics(); 55 WindowManager windowManager = 56 (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 57 58 Display display = windowManager.getDefaultDisplay(); 59 display.getRealMetrics(metrics); 60 61 store.addResult("width_pixels", metrics.widthPixels); 62 store.addResult("height_pixels", metrics.heightPixels); 63 store.addResult("x_dpi", metrics.xdpi); 64 store.addResult("y_dpi", metrics.ydpi); 65 store.addResult("density", metrics.density); 66 store.addResult("density_dpi", metrics.densityDpi); 67 68 Configuration configuration = getContext().getResources().getConfiguration(); 69 store.addResult("screen_size", getScreenSize(configuration)); 70 store.addResult("smallest_screen_width_dp", configuration.smallestScreenWidthDp); 71 72 // Add WindowManager Jetpack Library version and available display features. 73 addDisplayFeaturesIfPresent(store); 74 75 addPhysicalResolutionAndRefreshRate(store, display); 76 77 // Add device states from DeviceStateManager if available. 78 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 79 addDeviceStatesIfAvailable(store); 80 } 81 } 82 addDisplayFeaturesIfPresent(DeviceInfoStore store)83 private void addDisplayFeaturesIfPresent(DeviceInfoStore store) throws Exception { 84 // Try to get display features from extensions. If extensions is not present, try sidecar. 85 // If neither is available, do nothing. 86 // TODO (b/202855636) store info from both extensions and sidecar if both are present 87 if (ExtensionsUtil.isExtensionVersionValid()) { 88 // Extensions is available on device. 89 final int extensionVersion = ExtensionsUtil.getExtensionVersion(); 90 store.addResult("wm_jetpack_version", 91 "[Extensions]" + extensionVersion); 92 final Activity activity = ScreenDeviceInfo.this.launchActivity( 93 "com.android.compatibility.common.deviceinfo", 94 DummyActivity.class, 95 new Bundle()); 96 int[] displayFeatureTypes = ExtensionsUtil.getExtensionDisplayFeatureTypes(activity); 97 store.addArrayResult("display_features", displayFeatureTypes); 98 } else if (SidecarUtil.isSidecarVersionValid()) { 99 // Sidecar is available on device. 100 final Version sidecarVersion = SidecarUtil.getSidecarVersion(); 101 store.addResult("wm_jetpack_version", "[Sidecar]" + sidecarVersion.toString()); 102 final Activity activity = ScreenDeviceInfo.this.launchActivity( 103 "com.android.compatibility.common.deviceinfo", 104 DummyActivity.class, 105 new Bundle()); 106 int[] displayFeatureTypes = SidecarUtil.getSidecarDisplayFeatureTypes(activity); 107 store.addArrayResult("display_features", displayFeatureTypes); 108 } 109 } 110 111 @RequiresApi(Build.VERSION_CODES.S) addDeviceStatesIfAvailable(DeviceInfoStore store)112 private void addDeviceStatesIfAvailable(DeviceInfoStore store) throws IOException { 113 DeviceStateManager deviceStateManager = getContext().getSystemService( 114 DeviceStateManager.class); 115 116 // Get the supported device states on device if DeviceStateManager is available 117 if (deviceStateManager != null) { 118 store.addArrayResult("device_states", getDeviceStateIdentifiers(deviceStateManager)); 119 } 120 } 121 122 /** 123 * Returns the array of device state identifiers from {@link DeviceStateManager}. Due to GTS and 124 * ATS running tests on many different sdk-levels, this method may be running on a newer or 125 * older Android version, possibly bringing in issues if {@link DeviceStateManager}'s API 126 * surface has changed. This method uses reflection to call the correct API if that has 127 * occurred. 128 * 129 * b/329875626 for reference. 130 */ 131 @NonNull getDeviceStateIdentifiers(@onNull DeviceStateManager deviceStateManager)132 private int[] getDeviceStateIdentifiers(@NonNull DeviceStateManager deviceStateManager) { 133 try { 134 final List<DeviceState> deviceStates = deviceStateManager.getSupportedDeviceStates(); 135 final int[] identifiers = new int[deviceStates.size()]; 136 for (int i = 0; i < deviceStates.size(); i++) { 137 identifiers[i] = deviceStates.get(i).getIdentifier(); 138 } 139 return identifiers; 140 } catch (NoSuchMethodError e) { 141 return getDeviceStateIdentifiersUsingReflection(deviceStateManager); 142 } 143 } 144 145 /** 146 * Attempts to retrieve the array of device state identifiers from {@link DeviceStateManager} 147 * using reflection. 148 */ 149 @NonNull getDeviceStateIdentifiersUsingReflection( @onNull DeviceStateManager deviceStateManager)150 private int[] getDeviceStateIdentifiersUsingReflection( 151 @NonNull DeviceStateManager deviceStateManager) { 152 Method getSupportedStatesMethod = getMethod(deviceStateManager.getClass(), 153 "getSupportedStates"); 154 if (getSupportedStatesMethod == null) { 155 return new int[0]; 156 } 157 158 try { 159 final int[] identifiers = (int[]) getSupportedStatesMethod.invoke(deviceStateManager); 160 return identifiers; 161 } catch (Exception ignored) { 162 return new int[0]; 163 } 164 } 165 166 /** 167 * Returns the {@link Method} for the provided {@code methodName} on the provided 168 * {@code classToCheck}. If that method does not exist, return {@code null}; 169 */ 170 @Nullable getMethod(@onNull Class<?> classToCheck, @NonNull String methodName)171 private Method getMethod(@NonNull Class<?> classToCheck, @NonNull String methodName) { 172 try { 173 return classToCheck.getMethod(methodName); 174 } catch (NoSuchMethodException e) { 175 return null; 176 } 177 } 178 addPhysicalResolutionAndRefreshRate( DeviceInfoStore store, Display display)179 private static void addPhysicalResolutionAndRefreshRate( 180 DeviceInfoStore store, Display display) throws IOException { 181 if (ApiLevelUtil.isBefore(Build.VERSION_CODES.M)) { 182 return; 183 } 184 185 // Add properties of an active display mode. 186 Display.Mode activeMode = display.getMode(); 187 addDisplayModeProperties(store, activeMode, ""); 188 189 // Add properties of a supported mode with max resolution and refresh rate. 190 Display.Mode[] supportedModes = display.getSupportedModes(); 191 Comparator<Display.Mode> modeComparator = Comparator.comparingInt( 192 Display.Mode::getPhysicalWidth).thenComparingInt( 193 Display.Mode::getPhysicalHeight).thenComparingDouble( 194 Display.Mode::getRefreshRate); 195 Optional<Display.Mode> maxMode = Arrays.stream(supportedModes).max(modeComparator); 196 if (maxMode.isPresent()) { 197 addDisplayModeProperties(store, maxMode.get(), "max_"); 198 } 199 } 200 addDisplayModeProperties( DeviceInfoStore store, Display.Mode mode, String propertyPrefix)201 private static void addDisplayModeProperties( 202 DeviceInfoStore store, Display.Mode mode, String propertyPrefix) throws IOException { 203 store.addResult(propertyPrefix + "physical_width_pixels", mode.getPhysicalWidth()); 204 store.addResult(propertyPrefix + "physical_height_pixels", mode.getPhysicalHeight()); 205 store.addResult(propertyPrefix + "refresh_rate", mode.getRefreshRate()); 206 } 207 getScreenSize(Configuration configuration)208 private static String getScreenSize(Configuration configuration) { 209 int screenLayout = configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; 210 String screenSize = String.format("0x%x", screenLayout); 211 switch (screenLayout) { 212 case Configuration.SCREENLAYOUT_SIZE_SMALL: 213 screenSize = "small"; 214 break; 215 216 case Configuration.SCREENLAYOUT_SIZE_NORMAL: 217 screenSize = "normal"; 218 break; 219 220 case Configuration.SCREENLAYOUT_SIZE_LARGE: 221 screenSize = "large"; 222 break; 223 224 case Configuration.SCREENLAYOUT_SIZE_XLARGE: 225 screenSize = "xlarge"; 226 break; 227 228 case Configuration.SCREENLAYOUT_SIZE_UNDEFINED: 229 screenSize = "undefined"; 230 break; 231 } 232 return screenSize; 233 } 234 } 235