1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.O_MR1; 4 import static android.os.Build.VERSION_CODES.P; 5 import static android.os.Build.VERSION_CODES.Q; 6 import static org.robolectric.util.reflector.Reflector.reflector; 7 8 import android.graphics.Point; 9 import android.hardware.display.BrightnessChangeEvent; 10 import android.hardware.display.BrightnessConfiguration; 11 import android.hardware.display.DisplayManagerGlobal; 12 import android.hardware.display.IDisplayManager; 13 import android.hardware.display.IDisplayManagerCallback; 14 import android.hardware.display.IVirtualDisplayCallback; 15 import android.hardware.display.VirtualDisplayConfig; 16 import android.hardware.display.WifiDisplayStatus; 17 import android.media.projection.IMediaProjection; 18 import android.os.Handler; 19 import android.os.RemoteException; 20 import android.util.SparseArray; 21 import android.view.Display; 22 import android.view.DisplayInfo; 23 import com.google.common.annotations.VisibleForTesting; 24 import java.lang.reflect.Field; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.TreeMap; 30 import java.util.concurrent.CopyOnWriteArrayList; 31 import javax.annotation.Nullable; 32 import org.robolectric.RuntimeEnvironment; 33 import org.robolectric.android.Bootstrap; 34 import org.robolectric.annotation.ClassName; 35 import org.robolectric.annotation.HiddenApi; 36 import org.robolectric.annotation.Implementation; 37 import org.robolectric.annotation.Implements; 38 import org.robolectric.annotation.Resetter; 39 import org.robolectric.shadow.api.Shadow; 40 import org.robolectric.util.ReflectionHelpers; 41 import org.robolectric.util.reflector.Accessor; 42 import org.robolectric.util.reflector.ForType; 43 44 /** Shadow for {@link DisplayManagerGlobal}. */ 45 @Implements(value = DisplayManagerGlobal.class, isInAndroidSdk = false, looseSignatures = true) 46 public class ShadowDisplayManagerGlobal { 47 private static DisplayManagerGlobal instance; 48 49 private float saturationLevel = 1f; 50 private final SparseArray<BrightnessConfiguration> brightnessConfiguration = new SparseArray<>(); 51 private final List<BrightnessChangeEvent> brightnessChangeEvents = new ArrayList<>(); 52 private Object defaultBrightnessConfiguration; 53 54 private DisplayManagerProxyDelegate mDm; 55 56 @Resetter reset()57 public static void reset() { 58 instance = null; 59 } 60 61 @Implementation __constructor__(IDisplayManager dm)62 protected void __constructor__(IDisplayManager dm) { 63 // No-op the constructor. The real constructor references the ColorSpace named constants, which 64 // require native calls to instantiate. This will cause native graphics libraries to be loaded 65 // any time an Application object is created. Instead override the constructor to avoid 66 // referencing the ColorSpace named constants, making application creation around 0.75s faster. 67 } 68 69 @Implementation getInstance()70 public static synchronized DisplayManagerGlobal getInstance() { 71 if (instance == null) { 72 DisplayManagerProxyDelegate displayManagerProxyDelegate = new DisplayManagerProxyDelegate(); 73 IDisplayManager proxy = 74 ReflectionHelpers.createDelegatingProxy( 75 IDisplayManager.class, displayManagerProxyDelegate); 76 instance = newDisplayManagerGlobal(proxy); 77 ShadowDisplayManagerGlobal shadow = Shadow.extract(instance); 78 shadow.mDm = displayManagerProxyDelegate; 79 Bootstrap.setUpDisplay(); 80 } 81 return instance; 82 } 83 newDisplayManagerGlobal(IDisplayManager displayManager)84 private static DisplayManagerGlobal newDisplayManagerGlobal(IDisplayManager displayManager) { 85 instance = Shadow.newInstanceOf(DisplayManagerGlobal.class); 86 DisplayManagerGlobalReflector displayManagerGlobal = 87 reflector(DisplayManagerGlobalReflector.class, instance); 88 displayManagerGlobal.setDm(displayManager); 89 displayManagerGlobal.setLock(new Object()); 90 List<Handler> displayListeners = createDisplayListeners(); 91 displayManagerGlobal.setDisplayListeners(displayListeners); 92 displayManagerGlobal.setDisplayInfoCache(new SparseArray<>()); 93 return instance; 94 } 95 createDisplayListeners()96 private static List<Handler> createDisplayListeners() { 97 try { 98 // The type for mDisplayListeners was changed from ArrayList to CopyOnWriteArrayList 99 // in some branches of T and U, so we need to reflect on DisplayManagerGlobal class 100 // to check the type of mDisplayListeners member before initializing appropriately. 101 Field f = DisplayManagerGlobal.class.getDeclaredField("mDisplayListeners"); 102 if (f.getType().isAssignableFrom(ArrayList.class)) { 103 return new ArrayList<>(); 104 } else { 105 return new CopyOnWriteArrayList<>(); 106 } 107 } catch (NoSuchFieldException e) { 108 throw new RuntimeException(e); 109 } 110 } 111 112 @VisibleForTesting getGlobalInstance()113 static DisplayManagerGlobal getGlobalInstance() { 114 return instance; 115 } 116 117 @Implementation getWifiDisplayStatus()118 protected WifiDisplayStatus getWifiDisplayStatus() { 119 return new WifiDisplayStatus(); 120 } 121 122 /** Returns the 'natural' dimensions of the default display. */ 123 @Implementation(minSdk = O_MR1) getStableDisplaySize()124 public Point getStableDisplaySize() throws RemoteException { 125 DisplayInfo defaultDisplayInfo = mDm.getDisplayInfo(Display.DEFAULT_DISPLAY); 126 return new Point(defaultDisplayInfo.getNaturalWidth(), defaultDisplayInfo.getNaturalHeight()); 127 } 128 addDisplay(DisplayInfo displayInfo)129 int addDisplay(DisplayInfo displayInfo) { 130 fixNominalDimens(displayInfo); 131 132 return mDm.addDisplay(displayInfo); 133 } 134 fixNominalDimens(DisplayInfo displayInfo)135 private void fixNominalDimens(DisplayInfo displayInfo) { 136 int min = Math.min(displayInfo.appWidth, displayInfo.appHeight); 137 int max = Math.max(displayInfo.appWidth, displayInfo.appHeight); 138 displayInfo.smallestNominalAppHeight = displayInfo.smallestNominalAppWidth = min; 139 displayInfo.largestNominalAppHeight = displayInfo.largestNominalAppWidth = max; 140 } 141 changeDisplay(int displayId, DisplayInfo displayInfo)142 void changeDisplay(int displayId, DisplayInfo displayInfo) { 143 mDm.changeDisplay(displayId, displayInfo); 144 } 145 removeDisplay(int displayId)146 void removeDisplay(int displayId) { 147 mDm.removeDisplay(displayId); 148 } 149 150 /** 151 * A delegating proxy for the IDisplayManager system service. 152 * 153 * <p>The method signatures here must exactly match the IDisplayManager interface. 154 * 155 * @see ReflectionHelpers#createDelegatingProxy(Class, Object) 156 */ 157 private static class DisplayManagerProxyDelegate { 158 private final TreeMap<Integer, DisplayInfo> displayInfos = new TreeMap<>(); 159 private int nextDisplayId = 0; 160 private final List<IDisplayManagerCallback> callbacks = new ArrayList<>(); 161 private final Map<IVirtualDisplayCallback, Integer> virtualDisplayIds = new HashMap<>(); 162 163 // @Override getDisplayInfo(int i)164 public DisplayInfo getDisplayInfo(int i) throws RemoteException { 165 DisplayInfo displayInfo = displayInfos.get(i); 166 return displayInfo == null ? null : new DisplayInfo(displayInfo); 167 } 168 169 // @Override // todo: use @Implements/@Implementation for signature checking getDisplayIds()170 public int[] getDisplayIds() { 171 int[] ids = new int[displayInfos.size()]; 172 int i = 0; 173 for (Integer displayId : displayInfos.keySet()) { 174 ids[i++] = displayId; 175 } 176 return ids; 177 } 178 179 // Added in Android T 180 @SuppressWarnings("unused") getDisplayIds(boolean ignoredIncludeDisabled)181 public int[] getDisplayIds(boolean ignoredIncludeDisabled) { 182 return getDisplayIds(); 183 } 184 185 // @Override registerCallback(IDisplayManagerCallback iDisplayManagerCallback)186 public void registerCallback(IDisplayManagerCallback iDisplayManagerCallback) 187 throws RemoteException { 188 this.callbacks.add(iDisplayManagerCallback); 189 } 190 registerCallbackWithEventMask( IDisplayManagerCallback iDisplayManagerCallback, long ignoredEventsMask)191 public void registerCallbackWithEventMask( 192 IDisplayManagerCallback iDisplayManagerCallback, long ignoredEventsMask) 193 throws RemoteException { 194 registerCallback(iDisplayManagerCallback); 195 } 196 197 // for android U 198 // Use Object here instead of VirtualDisplayConfig to avoid breaking projects that still 199 // compile against SDKs < U createVirtualDisplay( @lassName"android.hardware.display.VirtualDisplayConfig") Object virtualDisplayConfigObject, IVirtualDisplayCallback callbackWrapper, IMediaProjection projectionToken, String packageName)200 public int createVirtualDisplay( 201 @ClassName("android.hardware.display.VirtualDisplayConfig") 202 Object virtualDisplayConfigObject, 203 IVirtualDisplayCallback callbackWrapper, 204 IMediaProjection projectionToken, 205 String packageName) { 206 VirtualDisplayConfig config = (VirtualDisplayConfig) virtualDisplayConfigObject; 207 DisplayInfo displayInfo = new DisplayInfo(); 208 displayInfo.flags = config.getFlags(); 209 displayInfo.type = Display.TYPE_VIRTUAL; 210 displayInfo.name = config.getName(); 211 displayInfo.logicalDensityDpi = config.getDensityDpi(); 212 displayInfo.physicalXDpi = config.getDensityDpi(); 213 displayInfo.physicalYDpi = config.getDensityDpi(); 214 displayInfo.ownerPackageName = packageName; 215 displayInfo.appWidth = config.getWidth(); 216 displayInfo.logicalWidth = config.getWidth(); 217 displayInfo.appHeight = config.getHeight(); 218 displayInfo.logicalHeight = config.getHeight(); 219 displayInfo.state = Display.STATE_ON; 220 int id = addDisplay(displayInfo); 221 virtualDisplayIds.put(callbackWrapper, id); 222 return id; 223 } 224 225 // for android U resizeVirtualDisplay( IVirtualDisplayCallback token, int width, int height, int densityDpi)226 public void resizeVirtualDisplay( 227 IVirtualDisplayCallback token, int width, int height, int densityDpi) { 228 Integer id = virtualDisplayIds.get(token); 229 DisplayInfo displayInfo = displayInfos.get(id); 230 231 displayInfo.logicalDensityDpi = densityDpi; 232 displayInfo.appWidth = width; 233 displayInfo.logicalWidth = width; 234 displayInfo.appHeight = height; 235 displayInfo.logicalHeight = height; 236 changeDisplay(id, displayInfo); 237 } 238 239 // for android U releaseVirtualDisplay(IVirtualDisplayCallback token)240 public void releaseVirtualDisplay(IVirtualDisplayCallback token) { 241 if (virtualDisplayIds.containsKey(token)) { 242 removeDisplay(virtualDisplayIds.remove(token)); 243 } 244 } 245 246 // @Override setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn)247 public void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) { 248 Integer id = virtualDisplayIds.get(token); 249 DisplayInfo displayInfo = displayInfos.get(id); 250 displayInfo.state = isOn ? Display.STATE_ON : Display.STATE_OFF; 251 changeDisplay(id, displayInfo); 252 } 253 addDisplay(DisplayInfo displayInfo)254 private synchronized int addDisplay(DisplayInfo displayInfo) { 255 int nextId = nextDisplayId++; 256 displayInfos.put(nextId, displayInfo); 257 if (RuntimeEnvironment.getApiLevel() >= Q) { 258 displayInfo.displayId = nextId; 259 } 260 notifyListeners(nextId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); 261 return nextId; 262 } 263 changeDisplay(int displayId, DisplayInfo displayInfo)264 private synchronized void changeDisplay(int displayId, DisplayInfo displayInfo) { 265 if (!displayInfos.containsKey(displayId)) { 266 throw new IllegalStateException("no display " + displayId); 267 } 268 269 displayInfos.put(displayId, displayInfo); 270 notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); 271 } 272 removeDisplay(int displayId)273 private synchronized void removeDisplay(int displayId) { 274 if (!displayInfos.containsKey(displayId)) { 275 throw new IllegalStateException("no display " + displayId); 276 } 277 278 displayInfos.remove(displayId); 279 notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); 280 } 281 notifyListeners(int nextId, int event)282 private void notifyListeners(int nextId, int event) { 283 for (IDisplayManagerCallback callback : callbacks) { 284 try { 285 callback.onDisplayEvent(nextId, event); 286 } catch (RemoteException e) { 287 throw new RuntimeException(e); 288 } 289 } 290 } 291 } 292 293 @Implementation(minSdk = P, maxSdk = P) setSaturationLevel(float level)294 protected void setSaturationLevel(float level) { 295 if (level < 0f || level > 1f) { 296 throw new IllegalArgumentException("Saturation level must be between 0 and 1"); 297 } 298 saturationLevel = level; 299 } 300 301 /** 302 * Returns the current display saturation level; {@link android.os.Build.VERSION_CODES.P} only. 303 */ getSaturationLevel()304 float getSaturationLevel() { 305 return saturationLevel; 306 } 307 308 @Implementation(minSdk = P) 309 @HiddenApi setBrightnessConfigurationForUser( Object configObject, Object userId, Object packageName)310 protected void setBrightnessConfigurationForUser( 311 Object configObject, Object userId, Object packageName) { 312 BrightnessConfiguration config = (BrightnessConfiguration) configObject; 313 brightnessConfiguration.put((int) userId, config); 314 } 315 316 @Implementation(minSdk = P) 317 @HiddenApi getBrightnessConfigurationForUser(int userId)318 protected Object getBrightnessConfigurationForUser(int userId) { 319 BrightnessConfiguration config = brightnessConfiguration.get(userId); 320 if (config != null) { 321 return config; 322 } else { 323 return getDefaultBrightnessConfiguration(); 324 } 325 } 326 327 @Implementation(minSdk = P) 328 @HiddenApi getDefaultBrightnessConfiguration()329 protected Object getDefaultBrightnessConfiguration() { 330 return defaultBrightnessConfiguration; 331 } 332 setDefaultBrightnessConfiguration(@ullable Object configObject)333 void setDefaultBrightnessConfiguration(@Nullable Object configObject) { 334 BrightnessConfiguration config = (BrightnessConfiguration) configObject; 335 defaultBrightnessConfiguration = config; 336 } 337 338 @Implementation(minSdk = P) 339 @HiddenApi getBrightnessEvents(String callingPackage)340 protected List<BrightnessChangeEvent> getBrightnessEvents(String callingPackage) { 341 return brightnessChangeEvents; 342 } 343 setBrightnessEvents(List<BrightnessChangeEvent> events)344 void setBrightnessEvents(List<BrightnessChangeEvent> events) { 345 brightnessChangeEvents.clear(); 346 brightnessChangeEvents.addAll(events); 347 } 348 349 @ForType(DisplayManagerGlobal.class) 350 interface DisplayManagerGlobalReflector { 351 @Accessor("mDm") setDm(IDisplayManager displayManager)352 void setDm(IDisplayManager displayManager); 353 354 @Accessor("mLock") setLock(Object lock)355 void setLock(Object lock); 356 357 @Accessor("mDisplayListeners") setDisplayListeners(List<Handler> list)358 void setDisplayListeners(List<Handler> list); 359 360 @Accessor("mDisplayInfoCache") setDisplayInfoCache(SparseArray<DisplayInfo> displayInfoCache)361 void setDisplayInfoCache(SparseArray<DisplayInfo> displayInfoCache); 362 } 363 } 364