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