All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowDisplayManagerGlobal Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.graphics.Point;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.display.WifiDisplayStatus;
import android.media.projection.IMediaProjection;
import android.os.Handler;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import com.google.common.annotations.VisibleForTesting;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.Bootstrap;
import org.robolectric.annotation.ClassName;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;

/** Shadow for {@link DisplayManagerGlobal}. */
@Implements(value = DisplayManagerGlobal.class, isInAndroidSdk = false)
public class ShadowDisplayManagerGlobal {
  private static DisplayManagerGlobal instance;

  private float saturationLevel = 1f;
  private final SparseArray brightnessConfiguration = new SparseArray<>();
  private final List brightnessChangeEvents = new ArrayList<>();
  private Object defaultBrightnessConfiguration;

  private DisplayManagerProxyDelegate mDm;

  @Resetter
  public static void reset() {
    instance = null;
  }

  @Implementation
  protected void __constructor__(IDisplayManager dm) {
    // No-op the constructor. The real constructor references the ColorSpace named constants, which
    // require native calls to instantiate. This will cause native graphics libraries to be loaded
    // any time an Application object is created. Instead override the constructor to avoid
    // referencing the ColorSpace named constants, making application creation around 0.75s faster.
  }

  @Implementation
  public static synchronized DisplayManagerGlobal getInstance() {
    if (instance == null) {
      DisplayManagerProxyDelegate displayManagerProxyDelegate = new DisplayManagerProxyDelegate();
      IDisplayManager proxy =
          ReflectionHelpers.createDelegatingProxy(
              IDisplayManager.class, displayManagerProxyDelegate);
      instance = newDisplayManagerGlobal(proxy);
      ShadowDisplayManagerGlobal shadow = Shadow.extract(instance);
      shadow.mDm = displayManagerProxyDelegate;
      Bootstrap.setUpDisplay();
    }
    return instance;
  }

  private static DisplayManagerGlobal newDisplayManagerGlobal(IDisplayManager displayManager) {
    instance = Shadow.newInstanceOf(DisplayManagerGlobal.class);
    DisplayManagerGlobalReflector displayManagerGlobal =
        reflector(DisplayManagerGlobalReflector.class, instance);
    displayManagerGlobal.setDm(displayManager);
    displayManagerGlobal.setLock(new Object());
    List displayListeners = createDisplayListeners();
    displayManagerGlobal.setDisplayListeners(displayListeners);
    displayManagerGlobal.setDisplayInfoCache(new SparseArray<>());
    return instance;
  }

  private static List createDisplayListeners() {
    try {
      // The type for mDisplayListeners was changed from ArrayList to CopyOnWriteArrayList
      // in some branches of T and U, so we need to reflect on DisplayManagerGlobal class
      // to check the type of mDisplayListeners member before initializing appropriately.
      Field f = DisplayManagerGlobal.class.getDeclaredField("mDisplayListeners");
      if (f.getType().isAssignableFrom(ArrayList.class)) {
        return new ArrayList<>();
      } else {
        return new CopyOnWriteArrayList<>();
      }
    } catch (NoSuchFieldException e) {
      throw new RuntimeException(e);
    }
  }

  @VisibleForTesting
  static DisplayManagerGlobal getGlobalInstance() {
    return instance;
  }

  @Implementation
  protected WifiDisplayStatus getWifiDisplayStatus() {
    return new WifiDisplayStatus();
  }

  /** Returns the 'natural' dimensions of the default display. */
  @Implementation(minSdk = O_MR1)
  public Point getStableDisplaySize() throws RemoteException {
    DisplayInfo defaultDisplayInfo = mDm.getDisplayInfo(Display.DEFAULT_DISPLAY);
    return new Point(defaultDisplayInfo.getNaturalWidth(), defaultDisplayInfo.getNaturalHeight());
  }

  int addDisplay(DisplayInfo displayInfo) {
    fixNominalDimens(displayInfo);

    return mDm.addDisplay(displayInfo);
  }

  private void fixNominalDimens(DisplayInfo displayInfo) {
    int min = Math.min(displayInfo.appWidth, displayInfo.appHeight);
    int max = Math.max(displayInfo.appWidth, displayInfo.appHeight);
    displayInfo.smallestNominalAppHeight = displayInfo.smallestNominalAppWidth = min;
    displayInfo.largestNominalAppHeight = displayInfo.largestNominalAppWidth = max;
  }

  void changeDisplay(int displayId, DisplayInfo displayInfo) {
    mDm.changeDisplay(displayId, displayInfo);
  }

  void removeDisplay(int displayId) {
    mDm.removeDisplay(displayId);
  }

  /**
   * A delegating proxy for the IDisplayManager system service.
   *
   * 

The method signatures here must exactly match the IDisplayManager interface. * * @see ReflectionHelpers#createDelegatingProxy(Class, Object) */ private static class DisplayManagerProxyDelegate { private final TreeMap displayInfos = new TreeMap<>(); private int nextDisplayId = 0; private final List callbacks = new ArrayList<>(); private final Map virtualDisplayIds = new HashMap<>(); // @Override public DisplayInfo getDisplayInfo(int i) throws RemoteException { DisplayInfo displayInfo = displayInfos.get(i); return displayInfo == null ? null : new DisplayInfo(displayInfo); } // @Override // todo: use @Implements/@Implementation for signature checking public int[] getDisplayIds() { int[] ids = new int[displayInfos.size()]; int i = 0; for (Integer displayId : displayInfos.keySet()) { ids[i++] = displayId; } return ids; } // Added in Android T @SuppressWarnings("unused") public int[] getDisplayIds(boolean ignoredIncludeDisabled) { return getDisplayIds(); } // @Override public void registerCallback(IDisplayManagerCallback iDisplayManagerCallback) throws RemoteException { this.callbacks.add(iDisplayManagerCallback); } public void registerCallbackWithEventMask( IDisplayManagerCallback iDisplayManagerCallback, long ignoredEventsMask) throws RemoteException { registerCallback(iDisplayManagerCallback); } // for android R+ (SDK 30+) // Use Object here instead of VirtualDisplayConfig to avoid breaking projects that still // compile against SDKs < R public int createVirtualDisplay( @ClassName("android.hardware.display.VirtualDisplayConfig") Object virtualDisplayConfigObject, IVirtualDisplayCallback callbackWrapper, IMediaProjection projectionToken, String packageName) { VirtualDisplayConfig config = (VirtualDisplayConfig) virtualDisplayConfigObject; DisplayInfo displayInfo = new DisplayInfo(); displayInfo.flags = config.getFlags(); displayInfo.type = Display.TYPE_VIRTUAL; displayInfo.name = config.getName(); displayInfo.logicalDensityDpi = config.getDensityDpi(); displayInfo.physicalXDpi = config.getDensityDpi(); displayInfo.physicalYDpi = config.getDensityDpi(); displayInfo.ownerPackageName = packageName; displayInfo.appWidth = config.getWidth(); displayInfo.logicalWidth = config.getWidth(); displayInfo.appHeight = config.getHeight(); displayInfo.logicalHeight = config.getHeight(); displayInfo.state = Display.STATE_ON; int id = addDisplay(displayInfo); virtualDisplayIds.put(callbackWrapper, id); return id; } // for android Q (SDK 29) and below public int createVirtualDisplay( IVirtualDisplayCallback callbackWrapper, IMediaProjection projectionToken, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.flags = flags; displayInfo.type = Display.TYPE_VIRTUAL; displayInfo.name = name; displayInfo.logicalDensityDpi = densityDpi; displayInfo.physicalXDpi = densityDpi; displayInfo.physicalYDpi = densityDpi; displayInfo.ownerPackageName = packageName; displayInfo.appWidth = width; displayInfo.logicalWidth = width; displayInfo.appHeight = height; displayInfo.logicalHeight = height; displayInfo.state = Display.STATE_ON; int id = addDisplay(displayInfo); virtualDisplayIds.put(callbackWrapper, id); return id; } // for android U public void resizeVirtualDisplay( IVirtualDisplayCallback token, int width, int height, int densityDpi) { Integer id = virtualDisplayIds.get(token); DisplayInfo displayInfo = displayInfos.get(id); displayInfo.logicalDensityDpi = densityDpi; displayInfo.appWidth = width; displayInfo.logicalWidth = width; displayInfo.appHeight = height; displayInfo.logicalHeight = height; changeDisplay(id, displayInfo); } // for android U public void releaseVirtualDisplay(IVirtualDisplayCallback token) { if (virtualDisplayIds.containsKey(token)) { removeDisplay(virtualDisplayIds.remove(token)); } } // for android Q through V // @Override public void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) { Integer id = virtualDisplayIds.get(token); DisplayInfo displayInfo = displayInfos.get(id); int newState = isOn ? Display.STATE_ON : Display.STATE_OFF; if (displayInfo.state != newState) { displayInfo.state = newState; changeDisplay(id, displayInfo); } } public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { // in post android V, the setVirtualDisplayState has been removed and the virtual device // state is propagated from system service // TODO: also check power group state if > android V setVirtualDisplayState(token, surface != null); } private synchronized int addDisplay(DisplayInfo displayInfo) { int nextId = nextDisplayId++; displayInfos.put(nextId, displayInfo); if (RuntimeEnvironment.getApiLevel() >= Q) { displayInfo.displayId = nextId; } notifyListeners(nextId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); return nextId; } private synchronized void changeDisplay(int displayId, DisplayInfo displayInfo) { if (!displayInfos.containsKey(displayId)) { throw new IllegalStateException("no display " + displayId); } displayInfos.put(displayId, displayInfo); notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); } private synchronized void removeDisplay(int displayId) { if (!displayInfos.containsKey(displayId)) { throw new IllegalStateException("no display " + displayId); } displayInfos.remove(displayId); notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); } private void notifyListeners(int nextId, int event) { for (IDisplayManagerCallback callback : callbacks) { try { callback.onDisplayEvent(nextId, event); } catch (RemoteException e) { throw new RuntimeException(e); } } } } @Implementation(minSdk = P, maxSdk = P) protected void setSaturationLevel(float level) { if (level < 0f || level > 1f) { throw new IllegalArgumentException("Saturation level must be between 0 and 1"); } saturationLevel = level; } /** * Returns the current display saturation level; {@link android.os.Build.VERSION_CODES.P} only. */ float getSaturationLevel() { return saturationLevel; } @Implementation(minSdk = P) @HiddenApi protected void setBrightnessConfigurationForUser( @ClassName("android.hardware.display.BrightnessConfiguration") Object configObject, int userId, String packageName) { BrightnessConfiguration config = (BrightnessConfiguration) configObject; brightnessConfiguration.put((int) userId, config); } @Implementation(minSdk = P) @HiddenApi protected @ClassName("android.hardware.display.BrightnessConfiguration") Object getBrightnessConfigurationForUser(int userId) { BrightnessConfiguration config = brightnessConfiguration.get(userId); if (config != null) { return config; } else { return getDefaultBrightnessConfiguration(); } } @Implementation(minSdk = P) @HiddenApi protected @ClassName("android.hardware.display.BrightnessConfiguration") Object getDefaultBrightnessConfiguration() { return defaultBrightnessConfiguration; } void setDefaultBrightnessConfiguration(@Nullable Object configObject) { BrightnessConfiguration config = (BrightnessConfiguration) configObject; defaultBrightnessConfiguration = config; } @Implementation(minSdk = P) @HiddenApi protected List getBrightnessEvents(String callingPackage) { return brightnessChangeEvents; } void setBrightnessEvents(List events) { brightnessChangeEvents.clear(); brightnessChangeEvents.addAll(events); } @ForType(DisplayManagerGlobal.class) interface DisplayManagerGlobalReflector { @Accessor("mDm") void setDm(IDisplayManager displayManager); @Accessor("mLock") void setLock(Object lock); @Accessor("mDisplayListeners") void setDisplayListeners(List list); @Accessor("mDisplayInfoCache") void setDisplayInfoCache(SparseArray displayInfoCache); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy