1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 4 5 import android.content.res.Configuration; 6 import android.hardware.display.DisplayManager; 7 import android.hardware.display.DisplayManagerGlobal; 8 import android.os.Build; 9 import android.util.DisplayMetrics; 10 import android.view.Display; 11 import android.view.DisplayInfo; 12 import android.view.Surface; 13 import org.robolectric.RuntimeEnvironment; 14 import org.robolectric.android.Bootstrap; 15 import org.robolectric.android.internal.DisplayConfig; 16 import org.robolectric.annotation.Implements; 17 import org.robolectric.res.Qualifiers; 18 import org.robolectric.shadow.api.Shadow; 19 import org.robolectric.util.Consumer; 20 21 /** 22 * For tests, display properties may be changed and devices may be added or removed 23 * programmatically. 24 */ 25 @Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1) 26 public class ShadowDisplayManager { 27 28 /** 29 * Adds a simulated display. 30 * 31 * @param qualifiersStr the {@link Qualifiers} string representing characteristics of the new 32 * display. 33 * @return the new display's ID 34 */ addDisplay(String qualifiersStr)35 public static int addDisplay(String qualifiersStr) { 36 return getShadowDisplayManagerGlobal().addDisplay(createDisplayInfo(qualifiersStr, null)); 37 } 38 39 /** internal only */ configureDefaultDisplay(Configuration configuration, DisplayMetrics displayMetrics)40 public static void configureDefaultDisplay(Configuration configuration, DisplayMetrics displayMetrics) { 41 ShadowDisplayManagerGlobal shadowDisplayManagerGlobal = getShadowDisplayManagerGlobal(); 42 if (DisplayManagerGlobal.getInstance().getDisplayIds().length != 0) { 43 throw new IllegalStateException("this method should only be called by Robolectric"); 44 } 45 46 shadowDisplayManagerGlobal.addDisplay(createDisplayInfo(configuration, displayMetrics)); 47 } 48 createDisplayInfo(Configuration configuration, DisplayMetrics displayMetrics)49 private static DisplayInfo createDisplayInfo(Configuration configuration, DisplayMetrics displayMetrics) { 50 int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density); 51 int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density); 52 53 DisplayInfo displayInfo = new DisplayInfo(); 54 displayInfo.name = "Built-in screen"; 55 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 56 displayInfo.uniqueId = "screen0"; 57 } 58 displayInfo.appWidth = widthPx; 59 displayInfo.appHeight = heightPx; 60 fixNominalDimens(displayInfo); 61 displayInfo.logicalWidth = widthPx; 62 displayInfo.logicalHeight = heightPx; 63 displayInfo.rotation = configuration.orientation == Configuration.ORIENTATION_PORTRAIT 64 ? Surface.ROTATION_0 65 : Surface.ROTATION_90; 66 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 67 displayInfo.modeId = 0; 68 displayInfo.defaultModeId = 0; 69 displayInfo.supportedModes = new Display.Mode[] { 70 new Display.Mode(0, widthPx, heightPx, 60) 71 }; 72 } 73 displayInfo.logicalDensityDpi = displayMetrics.densityDpi; 74 displayInfo.physicalXDpi = displayMetrics.densityDpi; 75 displayInfo.physicalYDpi = displayMetrics.densityDpi; 76 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 77 displayInfo.state = Display.STATE_ON; 78 } 79 80 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 81 displayInfo.getAppMetrics(displayMetrics); 82 } 83 84 return displayInfo; 85 } 86 fixNominalDimens(DisplayInfo displayInfo)87 private static void fixNominalDimens(DisplayInfo displayInfo) { 88 int smallest = Math.min(displayInfo.appWidth, displayInfo.appHeight); 89 int largest = Math.max(displayInfo.appWidth, displayInfo.appHeight); 90 91 displayInfo.smallestNominalAppWidth = smallest; 92 displayInfo.smallestNominalAppHeight = smallest; 93 displayInfo.largestNominalAppWidth = largest; 94 displayInfo.largestNominalAppHeight = largest; 95 } 96 createDisplayInfo(String qualifiersStr, DisplayInfo baseDisplayInfo)97 private static DisplayInfo createDisplayInfo(String qualifiersStr, DisplayInfo baseDisplayInfo) { 98 Configuration configuration = new Configuration(); 99 DisplayMetrics displayMetrics = new DisplayMetrics(); 100 101 if (qualifiersStr.startsWith("+") && baseDisplayInfo != null) { 102 configuration.orientation = 103 (baseDisplayInfo.rotation == Surface.ROTATION_0 104 || baseDisplayInfo.rotation == Surface.ROTATION_180) 105 ? Configuration.ORIENTATION_PORTRAIT 106 : Configuration.ORIENTATION_LANDSCAPE; 107 configuration.screenWidthDp = baseDisplayInfo.logicalWidth * DisplayMetrics.DENSITY_DEFAULT 108 / baseDisplayInfo.logicalDensityDpi; 109 configuration.screenHeightDp = baseDisplayInfo.logicalHeight * DisplayMetrics.DENSITY_DEFAULT 110 / baseDisplayInfo.logicalDensityDpi; 111 configuration.densityDpi = baseDisplayInfo.logicalDensityDpi; 112 displayMetrics.densityDpi = baseDisplayInfo.logicalDensityDpi; 113 displayMetrics.density = 114 baseDisplayInfo.logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; 115 } 116 117 Bootstrap.applyQualifiers(qualifiersStr, RuntimeEnvironment.getApiLevel(), configuration, 118 displayMetrics); 119 120 return createDisplayInfo(configuration, displayMetrics); 121 } 122 123 /** 124 * Changes properties of a simulated display. If `qualifiersStr` starts with a plus (`+`) sign, 125 * the display's previous configuration is modified with the given qualifiers; otherwise defaults 126 * are applied as described [here](http://robolectric.org/device-configuration/). 127 * 128 * 129 * @param displayId the display id to change 130 * @param qualifiersStr the {@link Qualifiers} string representing characteristics of the new 131 * display 132 */ changeDisplay(int displayId, String qualifiersStr)133 public static void changeDisplay(int displayId, String qualifiersStr) { 134 DisplayInfo baseDisplayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId); 135 DisplayInfo displayInfo = createDisplayInfo(qualifiersStr, baseDisplayInfo); 136 getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo); 137 } 138 139 /** 140 * Changes properties of a simulated display. The original properties will be passed to the 141 * `consumer`, which may modify them in place. The display will be updated with the new 142 * properties. 143 * 144 * @param displayId the display id to change 145 * @param consumer a function which modifies the display properties 146 */ changeDisplay(int displayId, Consumer<DisplayConfig> consumer)147 static void changeDisplay(int displayId, Consumer<DisplayConfig> consumer) { 148 DisplayInfo displayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId); 149 if (displayInfo != null) { 150 DisplayConfig displayConfig = new DisplayConfig(displayInfo); 151 consumer.accept(displayConfig); 152 displayConfig.copyTo(displayInfo); 153 fixNominalDimens(displayInfo); 154 } 155 156 getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo); 157 } 158 159 /** 160 * Removes a simulated display. 161 * 162 * @param displayId the display id to remove 163 */ removeDisplay(int displayId)164 public static void removeDisplay(int displayId) { 165 getShadowDisplayManagerGlobal().removeDisplay(displayId); 166 } 167 getShadowDisplayManagerGlobal()168 private static ShadowDisplayManagerGlobal getShadowDisplayManagerGlobal() { 169 if (Build.VERSION.SDK_INT < JELLY_BEAN_MR1) { 170 throw new UnsupportedOperationException("multiple displays not supported in Jelly Bean"); 171 } 172 173 return Shadow.extract(DisplayManagerGlobal.getInstance()); 174 } 175 } 176