• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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