Home | History | Annotate | Download | only in shadows
      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    */
     35   public static int addDisplay(String qualifiersStr) {
     36     return getShadowDisplayManagerGlobal().addDisplay(createDisplayInfo(qualifiersStr, null));
     37   }
     38 
     39   /** internal only */
     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 
     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 
     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 
     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    */
    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    */
    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    */
    164   public static void removeDisplay(int displayId) {
    165     getShadowDisplayManagerGlobal().removeDisplay(displayId);
    166   }
    167 
    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