package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;

import android.accounts.IAccountManager;
import android.app.IAlarmManager;
import android.app.INotificationManager;
import android.app.ISearchManager;
import android.app.admin.IDevicePolicyManager;
import android.app.job.IJobScheduler;
import android.app.slice.ISliceManager;
import android.app.trust.ITrustManager;
import android.app.usage.IUsageStatsManager;
import android.content.Context;
import android.content.IClipboard;
import android.content.IRestrictionsManager;
import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.input.IInputManager;
import android.hardware.usb.IUsbManager;
import android.location.ICountryDetector;
import android.location.ILocationManager;
import android.media.IAudioService;
import android.media.IMediaRouterService;
import android.media.session.ISessionManager;
import android.net.IConnectivityManager;
import android.net.INetworkScoreService;
import android.net.nsd.INsdManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.rtt.IWifiRttManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IInterface;
import android.os.IPowerManager;
import android.os.IUserManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.storage.IStorageManager;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.view.IInputMethodManager;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;

@SuppressWarnings("NewApi")
@Implements(value = ServiceManager.class, isInAndroidSdk = false)
public class ShadowServiceManager {

  private static final Map<String, IBinder> SERVICES;

  private static final Set<String> unavailableServices = new HashSet<>();

  static {
    Map<String, IBinder> map = new HashMap<>();
    map.put(
        Context.CLIPBOARD_SERVICE, createBinder(IClipboard.class, "android.content.IClipboard"));
    map.put(
        Context.WIFI_P2P_SERVICE,
        createBinder(IWifiP2pManager.class, "android.net.wifi.p2p.IWifiP2pManager"));
    map.put(
        Context.ACCOUNT_SERVICE,
        createBinder(IAccountManager.class, "android.accounts.IAccountManager"));
    map.put(
        Context.USB_SERVICE, createBinder(IUsbManager.class, "android.hardware.usb.IUsbManager"));
    map.put(
        Context.LOCATION_SERVICE,
        createBinder(ILocationManager.class, "android.location.ILocationManager"));
    map.put(
        Context.INPUT_METHOD_SERVICE,
        createBinder(IInputMethodManager.class, "com.android.internal.view.IInputMethodManager"));
    map.put(Context.ALARM_SERVICE, createBinder(IAlarmManager.class, "android.app.IAlarmManager"));
    map.put(Context.POWER_SERVICE, createBinder(IPowerManager.class, "android.os.IPowerManager"));
    map.put(
        BatteryStats.SERVICE_NAME,
        createBinder(IBatteryStats.class, "com.android.internal.app.IBatteryStats"));
    map.put(
        Context.DROPBOX_SERVICE,
        createBinder(
            IDropBoxManagerService.class, "com.android.internal.os.IDropBoxManagerService"));
    map.put(
        Context.DEVICE_POLICY_SERVICE,
        createBinder(IDevicePolicyManager.class, "android.app.admin.IDevicePolicyManager"));
    map.put(
        Context.CONNECTIVITY_SERVICE,
        createBinder(IConnectivityManager.class, "android.net.IConnectivityManager"));
    map.put(
        Context.WIFI_SERVICE, createBinder(IWifiManager.class, "android.net.wifi.IWifiManager"));
    map.put(
        Context.SEARCH_SERVICE, createBinder(ISearchManager.class, "android.app.ISearchManager"));
    map.put(
        Context.UI_MODE_SERVICE, createBinder(ISearchManager.class, "android.app.IUiModeManager"));
    map.put(
        Context.NETWORK_POLICY_SERVICE,
        createBinder(ISearchManager.class, "android.net.INetworkPolicyManager"));
    map.put(Context.INPUT_SERVICE, createBinder(IInputManager.class, "android.net.IInputManager"));
    map.put(
        Context.COUNTRY_DETECTOR,
        createBinder(ICountryDetector.class, "android.location.ICountryDetector"));
    map.put(
        Context.NSD_SERVICE, createBinder(INsdManager.class, "android.net.nsd.INsdManagerandroi"));
    map.put(
        Context.AUDIO_SERVICE, createBinder(IAudioService.class, "android.media.IAudioService"));
    map.put(
        Context.APPWIDGET_SERVICE,
        createBinder(IAppWidgetService.class, "com.android.internal.appwidget.IAppWidgetService"));
    map.put(Context.NOTIFICATION_SERVICE,
        createBinder(INotificationManager.class, "android.app.INotificationManager"));

    if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1) {
      map.put(Context.USER_SERVICE, createBinder(IUserManager.class, "android.os.IUserManager"));
    }
    if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
      map.put(
          Context.APP_OPS_SERVICE,
          createBinder(IAppOpsService.class, "com.android.internal.app.IAppOpsService"));
    }
    if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
      map.put(
          "batteryproperties",
          createBinder(
              IBatteryPropertiesRegistrar.class, "android.os.IBatteryPropertiesRegistrar"));
    }
    if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
      map.put(
          Context.RESTRICTIONS_SERVICE,
          createBinder(IRestrictionsManager.class, "android.content.IRestrictionsManager"));
      map.put(
          Context.TRUST_SERVICE,
          createBinder(ITrustManager.class, "android.app.trust.ITrustManager"));
      map.put(
          Context.JOB_SCHEDULER_SERVICE,
          createBinder(IJobScheduler.class, "android.app.job.IJobScheduler"));
      map.put(
          Context.NETWORK_SCORE_SERVICE,
          createBinder(INetworkScoreService.class, "android.net.INetworkScoreService"));
      map.put(
          Context.USAGE_STATS_SERVICE,
          createBinder(IUsageStatsManager.class, "android.app.usage.IUsageStatsManager"));
      map.put(
          Context.MEDIA_ROUTER_SERVICE,
          createBinder(IMediaRouterService.class, "android.media.IMediaRouterService"));
      map.put(
          Context.MEDIA_SESSION_SERVICE,
          createDeepBinder(ISessionManager.class, "android.media.session.ISessionManager"));
    }
    if (RuntimeEnvironment.getApiLevel() >= M) {
      map.put(
          Context.FINGERPRINT_SERVICE,
          createBinder(
              IFingerprintService.class, "android.hardware.fingerprint.IFingerprintService"));
    }
    if (RuntimeEnvironment.getApiLevel() >= N_MR1) {
      map.put(
          Context.SHORTCUT_SERVICE,
          createBinder(IShortcutService.class, "android.content.pm.IShortcutService"));
    }
    if (RuntimeEnvironment.getApiLevel() >= O) {
      map.put("mount", createBinder(IStorageManager.class, "android.os.storage.IStorageManager"));
    } else {
      map.put(
          "mount",
          createBinder("android.os.storage.IMountService", "android.os.storage.IMountService"));
    }
    if (RuntimeEnvironment.getApiLevel() >= P) {
      map.put(
          Context.SLICE_SERVICE,
          createBinder(ISliceManager.class, "android.app.slice.SliceManager"));
      map.put(
          Context.CROSS_PROFILE_APPS_SERVICE,
          createBinder(ICrossProfileApps.class, "android.content.pm.ICrossProfileApps"));
      map.put(
        Context.WIFI_RTT_RANGING_SERVICE,
        createBinder(IWifiRttManager.class, "android.net.wifi.IWifiRttManager"));
    }

    SERVICES = Collections.unmodifiableMap(map);
  }

  /**
   * Returns the binder associated with the given system service. If the given service is set to
   * unavailable in {@link #setServiceAvailability}, {@code null} will be returned.
   */
  @Implementation
  protected static IBinder getService(String name) {
    if (unavailableServices.contains(name)) {
      return null;
    }
    return SERVICES.get(name);
  }

  private static Binder createBinder(String className, String descriptor) {
    Class<IInterface> clazz;
    try {
      clazz = (Class<IInterface>) Class.forName(className);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    Binder binder = new Binder();
    binder.attachInterface(ReflectionHelpers.createNullProxy(clazz), descriptor);
    return binder;
  }

  private static Binder createBinder(Class<? extends IInterface> clazz, String descriptor) {
    Binder binder = new Binder();
    binder.attachInterface(ReflectionHelpers.createNullProxy(clazz), descriptor);
    return binder;
  }

  private static Binder createDeepBinder(Class<? extends IInterface> clazz, String descriptor) {
    Binder binder = new Binder();
    binder.attachInterface(ReflectionHelpers.createDeepProxy(clazz), descriptor);
    return binder;
  }

  @Implementation
  protected static void addService(String name, IBinder service) {}

  @Implementation
  protected static IBinder checkService(String name) {
    return null;
  }

  @Implementation
  protected static String[] listServices() throws RemoteException {
    return null;
  }

  @Implementation
  protected static void initServiceCache(Map<String, IBinder> cache) {}

  /**
   * Sets the availability of the given system service. If the service is set as unavailable,
   * subsequent calls to {@link Context#getSystemService} for that service will return {@code null}.
   */
  public static void setServiceAvailability(String service, boolean available) {
    if (available) {
      unavailableServices.remove(service);
    } else {
      unavailableServices.add(service);
    }
  }

  @Resetter
  public static void reset() {
    unavailableServices.clear();
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static org.robolectric.shadows.ShadowApplication.getInstance;

import android.os.PowerManager;
import android.os.WorkSource;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.robolectric.RuntimeEnvironment;
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;

@Implements(PowerManager.class)
public class ShadowPowerManager {
  private boolean isScreenOn = true;
  private boolean isInteractive = true;
  private boolean isPowerSaveMode = false;
  private boolean isDeviceIdleMode = false;
  private List<String> rebootReasons = new ArrayList<String>();
  private Map<String, Boolean> ignoringBatteryOptimizations = new HashMap<>();

  @Implementation
  protected PowerManager.WakeLock newWakeLock(int flags, String tag) {
    PowerManager.WakeLock wl = Shadow.newInstanceOf(PowerManager.WakeLock.class);
    ((ShadowWakeLock) Shadow.extract(wl)).setTag(tag);
    getInstance().addWakeLock(wl);
    return wl;
  }

  @Implementation
  protected boolean isScreenOn() {
    return isScreenOn;
  }

  public void setIsScreenOn(boolean screenOn) {
    isScreenOn = screenOn;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected boolean isInteractive() {
    return isInteractive;
  }

  public void setIsInteractive(boolean interactive) {
    isInteractive = interactive;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected boolean isPowerSaveMode() {
    return isPowerSaveMode;
  }

  public void setIsPowerSaveMode(boolean powerSaveMode) {
    isPowerSaveMode = powerSaveMode;
  }

  private Map<Integer, Boolean> supportedWakeLockLevels = new HashMap<>();

  @Implementation(minSdk = LOLLIPOP)
  protected boolean isWakeLockLevelSupported(int level) {
    return supportedWakeLockLevels.containsKey(level) ? supportedWakeLockLevels.get(level) : false;
  }

  public void setIsWakeLockLevelSupported(int level, boolean supported) {
    supportedWakeLockLevels.put(level, supported);
  }

  /**
   * @return `false` by default, or the value specified via {@link #setIsDeviceIdleMode(boolean)}
   */
  @Implementation(minSdk = M)
  protected boolean isDeviceIdleMode() {
    return isDeviceIdleMode;
  }

  /** Sets the value returned by {@link #isDeviceIdleMode()}. */
  public void setIsDeviceIdleMode(boolean isDeviceIdleMode) {
    this.isDeviceIdleMode = isDeviceIdleMode;
  }

  /** Discards the most recent {@code PowerManager.WakeLock}s */
  @Resetter
  public static void reset() {
    ShadowApplication shadowApplication = ShadowApplication.getInstance();
    if (shadowApplication != null) {
      shadowApplication.clearWakeLocks();
    }
  }

  /**
   * Retrieves the most recent wakelock registered by the application
   *
   * @return Most recent wake lock.
   */
  public static PowerManager.WakeLock getLatestWakeLock() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    return shadowApplication.getLatestWakeLock();
  }

  @Implementation(minSdk = M)
  protected boolean isIgnoringBatteryOptimizations(String packageName) {
    Boolean result = ignoringBatteryOptimizations.get(packageName);
    return result == null ? false : result;
  }

  public void setIgnoringBatteryOptimizations(String packageName, boolean value) {
    ignoringBatteryOptimizations.put(packageName, Boolean.valueOf(value));
  }

  @Implementation
  protected void reboot(String reason) {
    rebootReasons.add(reason);
  }

  /** Returns the number of times {@link #reboot(String)} was called. */
  public int getTimesRebooted() {
    return rebootReasons.size();
  }

  /** Returns the list of reasons for each reboot, in chronological order. */
  public ImmutableList<String> getRebootReasons() {
    return ImmutableList.copyOf(rebootReasons);
  }

  @Implements(PowerManager.WakeLock.class)
  public static class ShadowWakeLock {
    private boolean refCounted = true;
    private int refCount = 0;
    private boolean locked = false;
    private WorkSource workSource = null;
    private int timesHeld = 0;
    private String tag = null;

    @Implementation
    protected void acquire() {
      acquire(0);
    }

    @Implementation
    protected synchronized void acquire(long timeout) {
      ++timesHeld;
      if (refCounted) {
        refCount++;
      } else {
        locked = true;
      }
    }

    @Implementation
    protected synchronized void release() {
      if (refCounted) {
        if (--refCount < 0) throw new RuntimeException("WakeLock under-locked");
      } else {
        locked = false;
      }
    }

    @Implementation
    protected synchronized boolean isHeld() {
      return refCounted ? refCount > 0 : locked;
    }

    /**
     * Retrieves if the wake lock is reference counted or not
     *
     * @return Is the wake lock reference counted?
     */
    public boolean isReferenceCounted() {
      return refCounted;
    }

    @Implementation
    protected void setReferenceCounted(boolean value) {
      refCounted = value;
    }

    @Implementation
    protected synchronized void setWorkSource(WorkSource ws) {
      workSource = ws;
    }

    public synchronized WorkSource getWorkSource() {
      return workSource;
    }

    /** Returns how many times the wakelock was held. */
    public int getTimesHeld() {
      return timesHeld;
    }

    /** Returns the tag. */
    @HiddenApi
    @Implementation(minSdk = O)
    public String getTag() {
      return tag;
    }

    /** Sets the tag. */
    private void setTag(String tag) {
      this.tag = tag;
    }
  }
}


package org.robolectric.shadows;

import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.SoftThreadLocal;
import org.robolectric.util.TimeUtils;

/**
 * The {@link Choreographer} shadow for {@link LooperMode.Mode.PAUSED}.
 *
 *
In {@link LooperMode.Mode.PAUSED} mode, Robolectric maintains its own concept of the current
 * time from the Choreographer's point of view, aimed at making animations work correctly. Time
 * starts out at {@code 0} and advances by {@code frameInterval} every time {@link
 * Choreographer#getFrameTimeNanos()} is called.
 */
@Implements(
    value = Choreographer.class,
    shadowPicker = ShadowChoreographer.Picker.class,
    isInAndroidSdk = false)
public class ShadowLegacyChoreographer extends ShadowChoreographer {
  private long nanoTime = 0;
  private static long FRAME_INTERVAL = 10 * TimeUtils.NANOS_PER_MS; // 10ms
  private static final Thread MAIN_THREAD = Thread.currentThread();
  private static SoftThreadLocal<Choreographer> instance = makeThreadLocal();
  private Handler handler = new Handler(Looper.myLooper());
  private static volatile int postCallbackDelayMillis = 0;
  private static volatile int postFrameCallbackDelayMillis = 0;

  private static SoftThreadLocal<Choreographer> makeThreadLocal() {
    return new SoftThreadLocal<Choreographer>() {
      @Override
      protected Choreographer create() {
        Looper looper = Looper.myLooper();
        if (looper == null) {
          throw new IllegalStateException("The current thread must have a looper!");
        }

        // Choreographer's constructor changes somewhere in Android O...
        try {
          Choreographer.class.getDeclaredConstructor(Looper.class);
          return Shadow.newInstance(
              Choreographer.class, new Class[] {Looper.class}, new Object[] {looper});
        } catch (NoSuchMethodException e) {
          return Shadow.newInstance(
              Choreographer.class, new Class[] {Looper.class, int.class}, new Object[] {looper, 0});
        }
      }
    };
  }

  /**
   * Allows application to specify a fixed amount of delay when {@link #postCallback(int, Runnable,
   * Object)} is invoked. The default delay value is `0`. This can be used to avoid infinite
   * animation tasks to be spawned when the Robolectric {@link org.robolectric.util.Scheduler} is in
   * {@link org.robolectric.util.Scheduler.IdleState#PAUSED} mode.
   */
  public static void setPostCallbackDelay(int delayMillis) {
    postCallbackDelayMillis = delayMillis;
  }

  /**
   * Allows application to specify a fixed amount of delay when {@link
   * #postFrameCallback(FrameCallback)} is invoked. The default delay value is `0`. This can be used
   * to avoid infinite animation tasks to be spawned when the Robolectric {@link
   * org.robolectric.util.Scheduler} is in {@link org.robolectric.util.Scheduler.IdleState#PAUSED}
   * mode.
   */
  public static void setPostFrameCallbackDelay(int delayMillis) {
    postFrameCallbackDelayMillis = delayMillis;
  }

  @Implementation
  protected static Choreographer getInstance() {
    return instance.get();
  }

  /**
   * The default implementation will call {@link #postCallbackDelayed(int, Runnable, Object, long)}
   * with no delay. {@link android.animation.AnimationHandler} calls this method to schedule
   * animation updates infinitely. Because during a Robolectric test the system time is paused and
   * execution of the event loop is invoked for each test instruction, the behavior of
   * AnimationHandler would result in endless looping (the execution of the task results in a new
   * animation task created and scheduled to the front of the event loop queue).
   *
   *
To prevent endless looping, a test may call {@link #setPostCallbackDelay(int)} to specify a
   * small delay when animation is scheduled.
   *
   * @see #setPostCallbackDelay(int)
   */
  @Implementation
  protected void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, postCallbackDelayMillis);
  }

  @Implementation
  protected void postCallbackDelayed(
      int callbackType, Runnable action, Object token, long delayMillis) {
    handler.postDelayed(action, delayMillis);
  }

  @Implementation
  protected void removeCallbacks(int callbackType, Runnable action, Object token) {
    handler.removeCallbacks(action, token);
  }

  /**
   * The default implementation will call {@link #postFrameCallbackDelayed(FrameCallback, long)}
   * with no delay. {@link android.animation.AnimationHandler} calls this method to schedule
   * animation updates infinitely. Because during a Robolectric test the system time is paused and
   * execution of the event loop is invoked for each test instruction, the behavior of
   * AnimationHandler would result in endless looping (the execution of the task results in a new
   * animation task created and scheduled to the front of the event loop queue).
   *
   *
To prevent endless looping, a test may call {@link #setPostFrameCallbackDelay(int)} to
   * specify a small delay when animation is scheduled.
   *
   * @see #setPostCallbackDelay(int)
   */
  @Implementation
  protected void postFrameCallback(final FrameCallback callback) {
    postFrameCallbackDelayed(callback, postFrameCallbackDelayMillis);
  }

  @Implementation
  protected void postFrameCallbackDelayed(final FrameCallback callback, long delayMillis) {
    handler.postAtTime(
        new Runnable() {
          @Override
          public void run() {
            callback.doFrame(getFrameTimeNanos());
          }
        },
        callback,
        SystemClock.uptimeMillis() + delayMillis);
  }

  @Implementation
  protected void removeFrameCallback(FrameCallback callback) {
    handler.removeCallbacksAndMessages(callback);
  }

  @Implementation
  protected long getFrameTimeNanos() {
    final long now = nanoTime;
    nanoTime += ShadowLegacyChoreographer.FRAME_INTERVAL;
    return now;
  }

  /**
   * Return the current inter-frame interval.
   *
   * @return Inter-frame interval.
   */
  public static long getFrameInterval() {
    return ShadowLegacyChoreographer.FRAME_INTERVAL;
  }

  /**
   * Set the inter-frame interval used to advance the clock. By default, this is set to 1ms.
   *
   * @param frameInterval Inter-frame interval.
   */
  public static void setFrameInterval(long frameInterval) {
    ShadowLegacyChoreographer.FRAME_INTERVAL = frameInterval;
  }

  @Resetter
  public static synchronized void reset() {
    // Blech. We need to share the main looper because somebody might refer to it in a static
    // field. We also need to keep it in a soft reference so we don't max out permgen.
    if (Thread.currentThread() != MAIN_THREAD) {
      throw new RuntimeException("You should only call this from the main thread!");
    }
    instance = makeThreadLocal();
    FRAME_INTERVAL = 10 * TimeUtils.NANOS_PER_MS; // 10ms
  }
}


package org.robolectric.android;

import static com.google.common.base.Strings.isNullOrEmpty;

import android.content.res.Configuration;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
import java.util.Locale;
import org.robolectric.res.Qualifiers;
import org.robolectric.res.android.ConfigDescription;
import org.robolectric.res.android.ResTable_config;

/**
 * Supports device configuration for Robolectric tests.
 *
 * @see [Device Configuration](http://robolectric.org/device-configuration/)
 */
@SuppressWarnings("NewApi")
public class DeviceConfig {
  public static final int DEFAULT_DENSITY = ResTable_config.DENSITY_DPI_MDPI;
  public static final ScreenSize DEFAULT_SCREEN_SIZE = ScreenSize.normal;

  /**
   * Standard sizes for the
   * [screen size qualifier](https://developer.android.com/guide/topics/resources/providing-resources.html#ScreenSizeQualifier).
   */
  public enum ScreenSize {
    small(320, 426, Configuration.SCREENLAYOUT_SIZE_SMALL),
    normal(320, 470, Configuration.SCREENLAYOUT_SIZE_NORMAL),
    large(480, 640, Configuration.SCREENLAYOUT_SIZE_LARGE),
    xlarge(720, 960, Configuration.SCREENLAYOUT_SIZE_XLARGE);

    public final int width;
    public final int height;
    public final int landscapeWidth;
    public final int landscapeHeight;
    private final int configValue;

    ScreenSize(int width, int height, int configValue) {
      this.width = width;
      this.height = height;

      //noinspection SuspiciousNameCombination
      this.landscapeWidth = height;
      //noinspection SuspiciousNameCombination
      this.landscapeHeight = width;

      this.configValue = configValue;
    }

    private boolean isSmallerThanOrEqualTo(int x, int y) {
      if (y < x) {
        int oldY = y;
        //noinspection SuspiciousNameCombination
        y = x;
        //noinspection SuspiciousNameCombination
        x = oldY;
      }

      return width <= x && height <= y;
    }

    static ScreenSize find(int configValue) {
      switch (configValue) {
        case Configuration.SCREENLAYOUT_SIZE_SMALL:
          return small;
        case Configuration.SCREENLAYOUT_SIZE_NORMAL:
          return normal;
        case Configuration.SCREENLAYOUT_SIZE_LARGE:
          return large;
        case Configuration.SCREENLAYOUT_SIZE_XLARGE:
          return xlarge;
        case Configuration.SCREENLAYOUT_SIZE_UNDEFINED:
          return null;
        default:
          throw new IllegalArgumentException();
      }
    }

    static ScreenSize match(int x, int y) {
      ScreenSize bestMatch = small;

      for (ScreenSize screenSize : values()) {
        if (screenSize.isSmallerThanOrEqualTo(x, y)) {
          bestMatch = screenSize;
        }
      }

      return bestMatch;
    }
  }

  private DeviceConfig() {
  }

  static void applyToConfiguration(Qualifiers qualifiers, int apiLevel,
      Configuration configuration, DisplayMetrics displayMetrics) {
    ResTable_config resTab = qualifiers.getConfig();

    if (resTab.mcc != 0) {
      configuration.mcc = resTab.mcc;
    }

    if (resTab.mnc != 0) {
      configuration.mnc = resTab.mnc;
    }

    // screenLayout includes size, long, layoutdir, and round.
    // layoutdir may be overridden by setLocale(), so do this first:
    int screenLayoutSize = getScreenLayoutSize(configuration);
    int resTabSize = resTab.screenLayoutSize();
    if (resTabSize != ResTable_config.SCREENSIZE_ANY) {
      screenLayoutSize = resTabSize;

      if (resTab.screenWidthDp == 0) {
        configuration.screenWidthDp = 0;
      }

      if (resTab.screenHeightDp == 0) {
        configuration.screenHeightDp = 0;
      }
    }

    int screenLayoutLong = getScreenLayoutLong(configuration);
    int resTabLong = resTab.screenLayoutLong();
    if (resTabLong != ResTable_config.SCREENLONG_ANY) {
      screenLayoutLong = resTabLong;
    }

    int screenLayoutLayoutDir = getScreenLayoutLayoutDir(configuration);
    int resTabLayoutDir = resTab.screenLayoutDirection();
    if (resTabLayoutDir != ResTable_config.LAYOUTDIR_ANY) {
      screenLayoutLayoutDir = resTabLayoutDir;
    }

    int screenLayoutRound = getScreenLayoutRound(configuration);
    int resTabRound = resTab.screenLayoutRound();
    if (resTabRound != ResTable_config.SCREENROUND_ANY) {
      screenLayoutRound = resTabRound << 8;
    }

    configuration.screenLayout =
        screenLayoutSize | screenLayoutLong | screenLayoutLayoutDir | screenLayoutRound;

    // locale...
    String lang = resTab.languageString();
    String region = resTab.regionString();
    String script = resTab.scriptString();

    Locale locale;
    if (isNullOrEmpty(lang) && isNullOrEmpty(region) && isNullOrEmpty(script)) {
      locale = null;
    } else {
      locale = new Locale.Builder()
          .setLanguage(lang)
          .setRegion(region)
          .setScript(script == null ? "" : script)
          .build();
    }
    if (locale != null) {
      setLocale(apiLevel, configuration, locale);
    }

    if (resTab.smallestScreenWidthDp != 0) {
      configuration.smallestScreenWidthDp = resTab.smallestScreenWidthDp;
    }

    if (resTab.screenWidthDp != 0) {
      configuration.screenWidthDp = resTab.screenWidthDp;
    }

    if (resTab.screenHeightDp != 0) {
      configuration.screenHeightDp = resTab.screenHeightDp;
    }

    if (resTab.orientation != ResTable_config.ORIENTATION_ANY) {
      configuration.orientation = resTab.orientation;
    }

    // uiMode includes type and night...
    int uiModeType = getUiModeType(configuration);
    int resTabType = resTab.uiModeType();
    if (resTabType != ResTable_config.UI_MODE_TYPE_ANY) {
      uiModeType = resTabType;
    }

    int uiModeNight = getUiModeNight(configuration);
    int resTabNight = resTab.uiModeNight();
    if (resTabNight != ResTable_config.UI_MODE_NIGHT_ANY) {
      uiModeNight = resTabNight;
    }
    configuration.uiMode = uiModeType | uiModeNight;

    if (resTab.density != ResTable_config.DENSITY_DEFAULT) {
      setDensity(resTab.density, apiLevel, configuration, displayMetrics);
    }

    if (resTab.touchscreen != ResTable_config.TOUCHSCREEN_ANY) {
      configuration.touchscreen = resTab.touchscreen;
    }

    if (resTab.keyboard != ResTable_config.KEYBOARD_ANY) {
      configuration.keyboard = resTab.keyboard;
    }

    if (resTab.keyboardHidden() != ResTable_config.KEYSHIDDEN_ANY) {
      configuration.keyboardHidden = resTab.keyboardHidden();
    }

    if (resTab.navigation != ResTable_config.NAVIGATION_ANY) {
      configuration.navigation = resTab.navigation;
    }

    if (resTab.navigationHidden() != ResTable_config.NAVHIDDEN_ANY) {
      configuration.navigationHidden = resTab.navigationHidden();
    }

    if (apiLevel >= VERSION_CODES.O) {
      if (resTab.colorModeWideColorGamut() != ResTable_config.WIDE_COLOR_GAMUT_ANY) {
        setColorModeGamut(configuration, resTab.colorMode & ResTable_config.MASK_WIDE_COLOR_GAMUT);
      }

      if (resTab.colorModeHdr() != ResTable_config.HDR_ANY) {
        setColorModeHdr(configuration, resTab.colorMode & ResTable_config.MASK_HDR);
      }
    }
  }

  private static void setDensity(int densityDpi, int apiLevel, Configuration configuration,
      DisplayMetrics displayMetrics) {
    if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) {
      configuration.densityDpi = densityDpi;
    }
    displayMetrics.densityDpi = densityDpi;
    displayMetrics.density = displayMetrics.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
  }

  /**
   * Makes a given configuration, which may have undefined values, conform to the rules declared
   * [here](http://robolectric.org/device-configuration/).
   */
  static void applyRules(Configuration configuration, DisplayMetrics displayMetrics, int apiLevel) {
    Locale locale = getLocale(configuration, apiLevel);

    String language = locale == null ? "" : locale.getLanguage();
    if (language.isEmpty()) {
      language = "en";

      String country = locale == null ? "" : locale.getCountry();
      if (country.isEmpty()) {
        country = "us";
      }

      locale = new Locale(language, country);
      setLocale(apiLevel, configuration, locale);
    }

    if (apiLevel <= ConfigDescription.SDK_JELLY_BEAN &&
        getScreenLayoutLayoutDir(configuration) == Configuration.SCREENLAYOUT_LAYOUTDIR_UNDEFINED) {
      setScreenLayoutLayoutDir(configuration, Configuration.SCREENLAYOUT_LAYOUTDIR_LTR);
    }

    ScreenSize requestedScreenSize = getScreenSize(configuration);
    if (requestedScreenSize == null) {
      requestedScreenSize = DEFAULT_SCREEN_SIZE;
    }

    if (configuration.orientation == Configuration.ORIENTATION_UNDEFINED
        && configuration.screenWidthDp != 0 && configuration.screenHeightDp != 0) {
      configuration.orientation = (configuration.screenWidthDp > configuration.screenHeightDp)
          ? Configuration.ORIENTATION_LANDSCAPE
          : Configuration.ORIENTATION_PORTRAIT;
    }

    if (configuration.screenWidthDp == 0) {
      configuration.screenWidthDp = requestedScreenSize.width;
    }

    if (configuration.screenHeightDp == 0) {
      configuration.screenHeightDp = requestedScreenSize.height;

      if ((configuration.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
          == Configuration.SCREENLAYOUT_LONG_YES) {
        configuration.screenHeightDp = (int) (configuration.screenHeightDp * 1.25f);
      }
    }

    int lesserDimenPx = Math.min(configuration.screenWidthDp, configuration.screenHeightDp);
    int greaterDimenPx = Math.max(configuration.screenWidthDp, configuration.screenHeightDp);

    if (configuration.smallestScreenWidthDp == 0) {
      configuration.smallestScreenWidthDp = lesserDimenPx;
    }

    if (getScreenLayoutSize(configuration) == Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
      ScreenSize screenSize =
          ScreenSize.match(configuration.screenWidthDp, configuration.screenHeightDp);
      setScreenLayoutSize(configuration, screenSize.configValue);
    }

    if (getScreenLayoutLong(configuration) == Configuration.SCREENLAYOUT_LONG_UNDEFINED) {
      setScreenLayoutLong(configuration,
          ((float) greaterDimenPx) / lesserDimenPx >= 1.75
              ? Configuration.SCREENLAYOUT_LONG_YES
              : Configuration.SCREENLAYOUT_LONG_NO);
    }

    if (getScreenLayoutRound(configuration) == Configuration.SCREENLAYOUT_ROUND_UNDEFINED) {
      setScreenLayoutRound(configuration, Configuration.SCREENLAYOUT_ROUND_NO);
    }

    if (configuration.orientation == Configuration.ORIENTATION_UNDEFINED) {
      configuration.orientation = configuration.screenWidthDp > configuration.screenHeightDp
          ? Configuration.ORIENTATION_LANDSCAPE
          : Configuration.ORIENTATION_PORTRAIT;
    } else if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT
        && configuration.screenWidthDp > configuration.screenHeightDp) {
      swapXY(configuration);
    } else if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
        && configuration.screenWidthDp < configuration.screenHeightDp) {
      swapXY(configuration);
    }

    if (getUiModeType(configuration) == Configuration.UI_MODE_TYPE_UNDEFINED) {
      setUiModeType(configuration, Configuration.UI_MODE_TYPE_NORMAL);
    }

    if (getUiModeNight(configuration) == Configuration.UI_MODE_NIGHT_UNDEFINED) {
      setUiModeNight(configuration, Configuration.UI_MODE_NIGHT_NO);
    }

    switch (displayMetrics.densityDpi) {
      case ResTable_config.DENSITY_DPI_ANY:
        throw new IllegalArgumentException("'anydpi' isn't actually a dpi");
      case ResTable_config.DENSITY_DPI_NONE:
        throw new IllegalArgumentException("'nodpi' isn't actually a dpi");
      case ResTable_config.DENSITY_DPI_UNDEFINED:
        // DisplayMetrics.DENSITY_DEFAULT is mdpi
        setDensity(DEFAULT_DENSITY, apiLevel, configuration, displayMetrics);
    }

    if (configuration.touchscreen == Configuration.TOUCHSCREEN_UNDEFINED) {
      configuration.touchscreen = Configuration.TOUCHSCREEN_FINGER;
    }

    if (configuration.keyboardHidden == Configuration.KEYBOARDHIDDEN_UNDEFINED) {
      configuration.keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
    }

    if (configuration.keyboard == Configuration.KEYBOARD_UNDEFINED) {
      configuration.keyboard = Configuration.KEYBOARD_NOKEYS;
    }

    if (configuration.navigationHidden == Configuration.NAVIGATIONHIDDEN_UNDEFINED) {
      configuration.navigationHidden = Configuration.NAVIGATIONHIDDEN_YES;
    }

    if (configuration.navigation == Configuration.NAVIGATION_UNDEFINED) {
      configuration.navigation = Configuration.NAVIGATION_NONAV;
    }

    if (apiLevel >= VERSION_CODES.O) {
      if (getColorModeGamut(configuration) == Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED) {
        setColorModeGamut(configuration, Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
      }

      if (getColorModeHdr(configuration) == Configuration.COLOR_MODE_HDR_UNDEFINED) {
        setColorModeHdr(configuration, Configuration.COLOR_MODE_HDR_NO);
      }
    }
  }

  public static ScreenSize getScreenSize(Configuration configuration) {
    return ScreenSize.find(getScreenLayoutSize(configuration));
  }

  private static void swapXY(Configuration configuration) {
    int oldWidth = configuration.screenWidthDp;
    //noinspection SuspiciousNameCombination
    configuration.screenWidthDp = configuration.screenHeightDp;
    //noinspection SuspiciousNameCombination
    configuration.screenHeightDp = oldWidth;
  }

  private static void setLocale(int apiLevel, Configuration configuration, Locale locale) {
    if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) {
      configuration.setLocale(locale);
    } else {
      configuration.locale = locale;
    }
  }

  private static Locale getLocale(Configuration configuration, int apiLevel) {
    Locale locale;
    if (apiLevel > Build.VERSION_CODES.M) {
      locale = configuration.getLocales().get(0);
    } else {
      locale = configuration.locale;
    }
    return locale;
  }

  private static int getScreenLayoutSize(Configuration configuration) {
    return configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
  }

  private static void setScreenLayoutSize(Configuration configuration, int value) {
    configuration.screenLayout =
        (configuration.screenLayout & ~Configuration.SCREENLAYOUT_SIZE_MASK)
            | value;
  }

  private static int getScreenLayoutLong(Configuration configuration) {
    return configuration.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK;
  }

  private static void setScreenLayoutLong(Configuration configuration, int value) {
    configuration.screenLayout =
        (configuration.screenLayout & ~Configuration.SCREENLAYOUT_LONG_MASK)
            | value;
  }

  private static int getScreenLayoutLayoutDir(Configuration configuration) {
    return configuration.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
  }

  private static void setScreenLayoutLayoutDir(Configuration configuration, int value) {
    configuration.screenLayout =
        (configuration.screenLayout & ~Configuration.SCREENLAYOUT_LAYOUTDIR_MASK)
            | value;
  }

  private static int getScreenLayoutRound(Configuration configuration) {
    return configuration.screenLayout & Configuration.SCREENLAYOUT_ROUND_MASK;
  }

  private static void setScreenLayoutRound(Configuration configuration, int value) {
    configuration.screenLayout =
        (configuration.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
            | value;
  }

  private static int getUiModeType(Configuration configuration) {
    return configuration.uiMode & Configuration.UI_MODE_TYPE_MASK;
  }

  private static void setUiModeType(Configuration configuration, int value) {
    configuration.uiMode = (configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK) | value;
  }

  private static int getUiModeNight(Configuration configuration) {
    return configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
  }

  private static void setUiModeNight(Configuration configuration, int value) {
    configuration.uiMode = (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | value;
  }

  private static int getColorModeGamut(Configuration configuration) {
    return configuration.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK;
  }

  private static void setColorModeGamut(Configuration configuration, int value) {
    configuration.colorMode = (configuration.colorMode & ~Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) | value;
  }

  private static int getColorModeHdr(Configuration configuration) {
    return configuration.colorMode & Configuration.COLOR_MODE_HDR_MASK;
  }

  private static void setColorModeHdr(Configuration configuration, int value) {
    configuration.colorMode = (configuration.colorMode & ~Configuration.COLOR_MODE_HDR_MASK) | value;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.N;

import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

/**
 * Shadow of {@link android.view.accessibility.AccessibilityWindowInfo} that allows a test to set
 * properties that are locked in the original class.
 */
@Implements(value = AccessibilityWindowInfo.class, minSdk = LOLLIPOP)
public class ShadowAccessibilityWindowInfo {

  private static final Map<StrictEqualityWindowWrapper, StackTraceElement[]> obtainedInstances =
      new HashMap<>();

  private List<AccessibilityWindowInfo> children = null;

  private AccessibilityWindowInfo parent = null;

  private AccessibilityNodeInfo rootNode = null;

  private Rect boundsInScreen = new Rect();

  private int type = AccessibilityWindowInfo.TYPE_APPLICATION;

  private int layer = 0;

  private int id = 0;

  private CharSequence title = null;

  private boolean isAccessibilityFocused = false;

  private boolean isActive = false;

  private boolean isFocused = false;

  @RealObject
  private AccessibilityWindowInfo mRealAccessibilityWindowInfo;

  @Implementation
  protected void __constructor__() {}

  @Implementation
  protected static AccessibilityWindowInfo obtain() {
    final AccessibilityWindowInfo obtainedInstance =
        ReflectionHelpers.callConstructor(AccessibilityWindowInfo.class);
    StrictEqualityWindowWrapper wrapper = new StrictEqualityWindowWrapper(obtainedInstance);
    obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
    return obtainedInstance;
  }

  @Implementation
  protected static AccessibilityWindowInfo obtain(AccessibilityWindowInfo window) {
    final ShadowAccessibilityWindowInfo shadowInfo = Shadow.extract(window);
    final AccessibilityWindowInfo obtainedInstance = shadowInfo.getClone();
    StrictEqualityWindowWrapper wrapper = new StrictEqualityWindowWrapper(obtainedInstance);
    obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
    return obtainedInstance;
  }

  private AccessibilityWindowInfo getClone() {
    final AccessibilityWindowInfo newInfo =
        ReflectionHelpers.callConstructor(AccessibilityWindowInfo.class);
    final ShadowAccessibilityWindowInfo newShadow = Shadow.extract(newInfo);

    newShadow.boundsInScreen = new Rect(boundsInScreen);
    newShadow.parent = parent;
    newShadow.rootNode = rootNode;
    newShadow.type = type;
    newShadow.layer = layer;
    newShadow.id = id;
    newShadow.title = title;
    newShadow.isAccessibilityFocused = isAccessibilityFocused;
    newShadow.isActive = isActive;
    newShadow.isFocused = isFocused;

    return newInfo;
  }

  /**
   * Clear list of obtained instance objects. {@code areThereUnrecycledWindows} will always
   * return false if called immediately afterwards.
   */
  public static void resetObtainedInstances() {
    obtainedInstances.clear();
  }

  /**
   * Check for leaked objects that were {@code obtain}ed but never
   * {@code recycle}d.
   *
   * @param printUnrecycledWindowsToSystemErr - if true, stack traces of calls
   *        to {@code obtain} that lack matching calls to {@code recycle} are
   *        dumped to System.err.
   * @return {@code true} if there are unrecycled windows
   */
  public static boolean areThereUnrecycledWindows(boolean printUnrecycledWindowsToSystemErr) {
    if (printUnrecycledWindowsToSystemErr) {
      for (final StrictEqualityWindowWrapper wrapper : obtainedInstances.keySet()) {
        final ShadowAccessibilityWindowInfo shadow = Shadow.extract(wrapper.mInfo);

        System.err.println(String.format(
            "Leaked type = %d, id = %d. Stack trace:", shadow.getType(), shadow.getId()));
        for (final StackTraceElement stackTraceElement : obtainedInstances.get(wrapper)) {
          System.err.println(stackTraceElement.toString());
        }
      }
    }

    return (obtainedInstances.size() != 0);
  }

  @Override
  @Implementation
  @SuppressWarnings("ReferenceEquality")
  public boolean equals(Object object) {
    if (!(object instanceof AccessibilityWindowInfo)) {
      return false;
    }

    final AccessibilityWindowInfo window = (AccessibilityWindowInfo) object;
    final ShadowAccessibilityWindowInfo otherShadow = Shadow.extract(window);

    boolean areEqual = (type == otherShadow.getType());
    areEqual &= (parent == otherShadow.getParent());
    areEqual &= (rootNode == otherShadow.getRoot());
    areEqual &= (layer == otherShadow.getLayer());
    areEqual &= (id == otherShadow.getId());
    areEqual &= (title == otherShadow.getTitle());
    areEqual &= (isAccessibilityFocused == otherShadow.isAccessibilityFocused());
    areEqual &= (isActive == otherShadow.isActive());
    areEqual &= (isFocused == otherShadow.isFocused());
    Rect anotherBounds = new Rect();
    otherShadow.getBoundsInScreen(anotherBounds);
    areEqual &= (boundsInScreen.equals(anotherBounds));
    return areEqual;
  }

  @Override
  @Implementation
  public int hashCode() {
    // This is 0 for a reason. If you change it, you will break the obtained instances map in
    // a manner that is remarkably difficult to debug. Having a dynamic hash code keeps this
    // object from being located in the map if it was mutated after being obtained.
    return 0;
  }

  @Implementation
  protected int getType() {
    return type;
  }

  @Implementation
  protected int getChildCount() {
    if (children == null) {
      return 0;
    }

    return children.size();
  }

  @Implementation
  protected AccessibilityWindowInfo getChild(int index) {
    if (children == null) {
      return null;
    }

    return children.get(index);
  }

  @Implementation
  protected AccessibilityWindowInfo getParent() {
    return parent;
  }

  @Implementation
  protected AccessibilityNodeInfo getRoot() {
    return (rootNode == null) ? null : AccessibilityNodeInfo.obtain(rootNode);
  }

  @Implementation
  protected boolean isActive() {
    return isActive;
  }

  @Implementation
  protected int getId() {
    return id;
  }

  @Implementation
  protected void getBoundsInScreen(Rect outBounds) {
    if (boundsInScreen == null) {
      outBounds.setEmpty();
    } else {
      outBounds.set(boundsInScreen);
    }
  }

  @Implementation
  protected int getLayer() {
    return layer;
  }

  /** Returns the title of this window, or {@code null} if none is available. */
  @Implementation(minSdk = N)
  protected CharSequence getTitle() {
    return title;
  }

  @Implementation
  protected boolean isFocused() {
    return isFocused;
  }

  @Implementation
  protected boolean isAccessibilityFocused() {
    return isAccessibilityFocused;
  }

  @Implementation
  protected void recycle() {
    // This shadow does not track recycling of windows.
  }

  public void setRoot(AccessibilityNodeInfo root) {
    rootNode = root;
  }

  public void setType(int value) {
    type = value;
  }

  public void setBoundsInScreen(Rect bounds) {
    boundsInScreen.set(bounds);
  }

  public void setAccessibilityFocused(boolean value) {
    isAccessibilityFocused = value;
  }

  public void setActive(boolean value) {
    isActive = value;
  }

  public void setId(int value) {
    id = value;
  }

  public void setLayer(int value) {
    layer = value;
  }

  /**
   * Sets the title of this window.
   *
   * @param value The {@link CharSequence} to set as the title of this window
   */
  public void setTitle(CharSequence value) {
    title = value;
  }

  public void setFocused(boolean focused) {
    isFocused = focused;
  }

  public void addChild(AccessibilityWindowInfo child) {
    if (children == null) {
      children = new ArrayList<>();
    }

    children.add(child);
    ((ShadowAccessibilityWindowInfo) Shadow.extract(child)).parent =
        mRealAccessibilityWindowInfo;
  }

  /**
   * Private class to keep different windows referring to the same window straight
   * in the mObtainedInstances map.
   */
  private static class StrictEqualityWindowWrapper {
    public final AccessibilityWindowInfo mInfo;

    public StrictEqualityWindowWrapper(AccessibilityWindowInfo info) {
      mInfo = info;
    }

    @Override
    @SuppressWarnings("ReferenceEquality")
    public boolean equals(Object object) {
      if (object == null) {
        return false;
      }

      final StrictEqualityWindowWrapper wrapper = (StrictEqualityWindowWrapper) object;
      return mInfo == wrapper.mInfo;
    }

    @Override
    public int hashCode() {
      return mInfo.hashCode();
    }
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.os.Build;
import android.os.Message;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;

/**
 * The shadow {@link Message} for {@link LooperMode.Mode.PAUSED}.
 *
 * <p>This class should not be referenced directly. Use {@link ShadowMessage} instead.
 */
@Implements(value = Message.class, isInAndroidSdk = false)
public class ShadowPausedMessage extends ShadowMessage {

  private @RealObject Message realObject;

  long getWhen() {
    return reflector(ReflectorMessage.class, realObject).getWhen();
  }

  Message internalGetNext() {
    return reflector(ReflectorMessage.class, realObject).getNext();
  }

  // TODO: reconsider this being exposed as a public method
  @Override
  @Implementation(minSdk = LOLLIPOP)
  public void recycleUnchecked() {
    if (Build.VERSION.SDK_INT >= LOLLIPOP) {
      directlyOn(realObject, Message.class, "recycleUnchecked");
    } else {
      directlyOn(realObject, Message.class).recycle();
    }
  }

  @Override
  public void setScheduledRunnable(Runnable r) {
    throw new UnsupportedOperationException("Not supported in PAUSED LooperMode");
  }

  // we could support these methods, but intentionally do not for now as its unclear what the
  // use case is.

  @Override
  public Message getNext() {
    throw new UnsupportedOperationException("Not supported in PAUSED LooperMode");
  }

  @Override
  public void setNext(Message next) {
    throw new UnsupportedOperationException("Not supported in PAUSED LooperMode");
  }

  /** Accessor interface for {@link Message}'s internals. */
  @ForType(Message.class)
  private interface ReflectorMessage {

    @Accessor("when")
    long getWhen();

    @Accessor("next")
    Message getNext();
  }
}


package org.robolectric.android;

import android.view.View;
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils;
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult;
import com.google.android.apps.common.testing.accessibility.framework.DuplicateClickableBoundsViewCheck;
import com.google.android.apps.common.testing.accessibility.framework.TouchTargetSizeViewCheck;
import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.robolectric.annotation.AccessibilityChecks;
import org.robolectric.annotation.AccessibilityChecks.ForRobolectricVersion;

/**
 * Utility class for checking Views for accessibility.
 *
 * This class is used by {@code ShadowView.checkedPerformClick} to check for accessibility problems.
 * There is some subtlety to checking a UI for accessibility when it hasn't been rendered. The
 * better initialized the View, the more accurate the checking will be. At a minimum, the view
 * should be attached to a proper view hierarchy similar to what's checked for in:q
 * {@code ShadowView.checkedPerformClick}.
 */
public class AccessibilityUtil {
  private static final String COMPAT_V4_CLASS_NAME = "android.support.v4.view.ViewCompat";
  /* The validator that this class configures and uses to run the checks */
  private static AccessibilityValidator validator;

  /*
   * Slightly hacky way to deal with the legacy of allowing the annotation to configure the
   * subset of checks to run from the annotation. {@code true} when a version set is
   * specified by setRunChecksForRobolectricVersion.
   */
  private static boolean forVersionSet = false;

  /* Flag indicating if the support library's presence has been verified */
  private static boolean v4SupportPresenceVerified = false;

  protected AccessibilityUtil() {}

  /**
   * Check a hierarchy of {@code View}s for accessibility. Only performs checks if (in decreasing
   * priority order) accessibility checking is enabled using an {@link AccessibilityChecks}
   * annotation, if the system property {@code robolectric.accessibility.enablechecks} is set to
   * {@code true}, or if the environment variable {@code robolectric.accessibility.enablechecks}
   * is set to {@code true}.
   *
   * @param view The {@code View} to examine
   *
   * @return A list of results from the check. If there are no results or checking is disabled,
   * the list is empty.
   */
  public static List<AccessibilityViewCheckResult> checkViewIfCheckingEnabled(View view) {
    AccessibilityChecks classChecksAnnotation = getAnnotation();
    if (!isAccessibilityCheckingEnabled(classChecksAnnotation)) {
      return Collections.emptyList();
    }

    return checkView(view);
  }

  /**
   * Check a hierarchy of {@code View}s for accessibility, based on currently set options.
   *
   * @param view The {@code View} to examine
   *
   * @return A list of results from the check. If there are no results, the list is empty.
   */
  public static List<AccessibilityViewCheckResult> checkView(View view) {
    return checkView(view, getAnnotation());
  }

  /**
   * Check a hierarchy of {@code View}s for accessibility. Only performs checks if (in decreasing
   * priority order) accessibility checking is enabled using an {@link AccessibilityChecks}
   * annotation, if the system property {@code robolectric.accessibility.enablechecks} is set to
   * {@code true}, or if the environment variable {@code robolectric.accessibility.enablechecks}
   * is set to {@code true}.
   *
   * Implicitly calls {code setThrowExceptionForErrors(false)} to disable exception throwing. This
   * method is deprecated, both because of this side effect and because the other methods offer
   * more control over execution.
   *
   * @param view The {@code View} to examine
   *
   * @return A list of results from the check. If there are no results or checking is disabled,
   * the list is empty.
   */
  @Deprecated
  public static boolean passesAccessibilityChecksIfEnabled(View view) {
    setThrowExceptionForErrors(false);
    List<AccessibilityViewCheckResult> results = checkViewIfCheckingEnabled(view);
    List<AccessibilityViewCheckResult> errors = AccessibilityCheckResultUtils.getResultsForType(
        results, AccessibilityCheckResultType.ERROR);
    return (errors.size() == 0);
  }

  /**
   * Specify that a specific subset of accessibility checks be run. The subsets are specified based
   * on which Robolectric version particular checks were released with. By default, all checks are
   * run {@link ForRobolectricVersion}.
   *
   * If you call this method, the value you pass will take precedence over any value in any
   * annotations.
   *
   * @param forVersion The version of checks to run for. If {@code null}, throws away the current
   * value and falls back on the annotation or default.
   */
  public static void setRunChecksForRobolectricVersion(ForRobolectricVersion forVersion) {
    initializeValidator();
    if (forVersion != null) {
      validator.setCheckPreset(convertRoboVersionToA11yTestVersion(forVersion));
      forVersionSet = true;
    } else {
      forVersionSet = false;
    }
  }

  /**
   * Specify that accessibility checks should be run for all views in the hierarchy whenever a
   * single view's accessibility is asserted.
   *
   * @param runChecksFromRootView {@code true} if all views in the hierarchy should be checked.
   */
  public static void setRunChecksFromRootView(boolean runChecksFromRootView) {
    initializeValidator();
    validator.setRunChecksFromRootView(runChecksFromRootView);
  }

  /**
   * Suppress all results that match the given matcher. Suppressed results will not be included
   * in any logs or cause any {@code Exception} to be thrown. This capability is useful if there
   * are known issues, but checks should still look for regressions.
   *
   * @param matcher A matcher to match a {@link AccessibilityViewCheckResult}. {@code null}
   * disables suppression and is the default.
   */
  @SuppressWarnings("unchecked") // The generic passed to anyOf
  public static void setSuppressingResultMatcher(
      final Matcher<? super AccessibilityViewCheckResult> matcher) {
    initializeValidator();
    /* Suppress all touch target results, since views all report size as 0x0 */
    Matcher<AccessibilityCheckResult> touchTargetResultMatcher =
        AccessibilityCheckResultUtils.matchesChecks(
            Matchers.equalTo(TouchTargetSizeViewCheck.class));
    Matcher<AccessibilityCheckResult> duplicateBoundsResultMatcher =
        AccessibilityCheckResultUtils.matchesChecks(
            Matchers.equalTo(DuplicateClickableBoundsViewCheck.class));
    if (matcher == null) {
      validator.setSuppressingResultMatcher(
          Matchers.anyOf(touchTargetResultMatcher, duplicateBoundsResultMatcher));
    } else {
      validator.setSuppressingResultMatcher(
          Matchers.anyOf(matcher, touchTargetResultMatcher, duplicateBoundsResultMatcher));
    }
  }

  /**
   * Control whether or not to throw exceptions when accessibility errors are found.
   *
   * @param throwExceptionForErrors {@code true} to throw an {@code AccessibilityViewCheckException}
   * when there is at least one error result. Default: {@code true}.
   */
  public static void setThrowExceptionForErrors(boolean throwExceptionForErrors) {
    initializeValidator();
    validator.setThrowExceptionForErrors(throwExceptionForErrors);
  }

  private static List<AccessibilityViewCheckResult> checkView(View view,
      AccessibilityChecks classChecksAnnotation) {
    /*
     * Accessibility Checking requires the v4 support library. If the support library isn't present,
     * throw a descriptive exception now.
     */
    if (!v4SupportPresenceVerified) {
      try {
        View.class.getClassLoader().loadClass(COMPAT_V4_CLASS_NAME);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(
            "Accessibility Checking requires the Android support library (v4).\n"
            + "Either include it in the project or disable accessibility checking.");
      }
      v4SupportPresenceVerified = true;
    }

    initializeValidator();
    if (!forVersionSet) {
      if (classChecksAnnotation != null) {
        validator.setCheckPreset(
            convertRoboVersionToA11yTestVersion(classChecksAnnotation.forRobolectricVersion()));
      } else {
        validator.setCheckPreset(AccessibilityCheckPreset.LATEST);
      }
    }
    return validator.checkAndReturnResults(view);
  }

  private static boolean isAccessibilityCheckingEnabled(AccessibilityChecks classChecksAnnotation) {
    boolean checksEnabled = false;

    String checksEnabledString = System.getenv("robolectric.accessibility.enablechecks");
    if (checksEnabledString != null) {
      checksEnabled = checksEnabledString.equals("true");
    }

    /* Allow test arg to enable checking (and override environment variables) */
    checksEnabledString = System.getProperty("robolectric.accessibility.enablechecks");
    if (checksEnabledString != null) {
      checksEnabled = checksEnabledString.equals("true");
    }

    if (classChecksAnnotation != null) {
      checksEnabled = classChecksAnnotation.enabled();
    }

    return checksEnabled;
  }

  private static AccessibilityChecks getAnnotation() {
    AccessibilityChecks classChecksAnnotation = null;
    StackTraceElement[] stack = new Throwable().fillInStackTrace().getStackTrace();
    for (StackTraceElement element : stack) {
      /* Look for annotations on the method or the class */
      Class<?> clazz;
      try {
        clazz = Class.forName(element.getClassName());
        Method method;
        method = clazz.getMethod(element.getMethodName());
        /* Assume the method is void, as that is the case for tests */
        classChecksAnnotation = method.getAnnotation(AccessibilityChecks.class);
        if (classChecksAnnotation == null) {
          classChecksAnnotation = clazz.getAnnotation(AccessibilityChecks.class);
        }
        /* Stop looking when we find an annotation */
        if (classChecksAnnotation != null) {
          break;
        }

        /* If we've crawled up the stack far enough to find the test, stop looking */
        for (Annotation annotation : clazz.getAnnotations()) {
          if (annotation.annotationType().getName().equals("org.junit.Test")) {
            break;
          }
        }
      }
      /*
       * The reflective calls may throw exceptions if the stack trace elements
       * don't look like junit test methods. In that case we simply go on
       * to the next element
       */
      catch (ClassNotFoundException | SecurityException | NoSuchMethodException e) {}
    }
    return classChecksAnnotation;
  }

  private static void initializeValidator() {
    if (validator == null) {
      validator = new AccessibilityValidator();
      setSuppressingResultMatcher(null);
    }
  }

  private static AccessibilityCheckPreset convertRoboVersionToA11yTestVersion(
      ForRobolectricVersion robolectricVersion) {
    if (robolectricVersion == ForRobolectricVersion.LATEST) {
      return AccessibilityCheckPreset.LATEST;
    }
    AccessibilityCheckPreset preset = AccessibilityCheckPreset.VERSION_1_0_CHECKS;
    if (robolectricVersion.ordinal() >= ForRobolectricVersion.VERSION_3_1.ordinal()) {
      preset = AccessibilityCheckPreset.VERSION_2_0_CHECKS;
    }
    return preset;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorDescription;
import android.accounts.AuthenticatorException;
import android.accounts.IAccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.util.Scheduler.IdleState;

@Implements(AccountManager.class)
public class ShadowAccountManager {

  private List<Account> accounts = new ArrayList<>();
  private Map<Account, Map<String, String>> authTokens = new HashMap<>();
  private Map<String, AuthenticatorDescription> authenticators = new LinkedHashMap<>();
  private List<OnAccountsUpdateListener> listeners = new ArrayList<>();
  private Map<Account, Map<String, String>> userData = new HashMap<>();
  private Map<Account, String> passwords = new HashMap<>();
  private Map<Account, Set<String>> accountFeatures = new HashMap<>();
  private Map<Account, Set<String>> packageVisibileAccounts = new HashMap<>();

  private List<Bundle> addAccountOptionsList = new ArrayList<>();
  private Handler mainHandler;
  private RoboAccountManagerFuture pendingAddFuture;
  private boolean authenticationErrorOnNextResponse = false;
  private Intent removeAccountIntent;

  @Implementation
  protected void __constructor__(Context context, IAccountManager service) {
    mainHandler = new Handler(context.getMainLooper());
  }

  /**
   * @deprecated This method will be removed in Robolectric 3.4 Use {@link
   *     AccountManager#get(Context)} instead.
   */
  @Deprecated
  @Implementation
  protected static AccountManager get(Context context) {
    return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
  }

  @Implementation
  protected Account[] getAccounts() {
    return accounts.toArray(new Account[accounts.size()]);
  }

  @Implementation
  protected Account[] getAccountsByType(String type) {
    if (type == null) {
      return getAccounts();
    }
    List<Account> accountsByType = new ArrayList<>();

    for (Account a : accounts) {
      if (type.equals(a.type)) {
        accountsByType.add(a);
      }
    }

    return accountsByType.toArray(new Account[accountsByType.size()]);
  }

  @Implementation
  protected synchronized void setAuthToken(Account account, String tokenType, String authToken) {
    if (accounts.contains(account)) {
      Map<String, String> tokenMap = authTokens.get(account);
      if (tokenMap == null) {
        tokenMap = new HashMap<>();
        authTokens.put(account, tokenMap);
      }
      tokenMap.put(tokenType, authToken);
    }
  }

  @Implementation
  protected String peekAuthToken(Account account, String tokenType) {
    Map<String, String> tokenMap = authTokens.get(account);
    if (tokenMap != null) {
      return tokenMap.get(tokenType);
    }
    return null;
  }

  @SuppressWarnings("InconsistentCapitalization")
  @Implementation
  protected boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }
    for (Account a : getAccountsByType(account.type)) {
      if (a.name.equals(account.name)) {
        return false;
      }
    }

    if (!accounts.add(account)) {
      return false;
    }

    setPassword(account, password);

    if (userdata != null) {
      for (String key : userdata.keySet()) {
        setUserData(account, key, userdata.get(key).toString());
      }
    }

    notifyListeners();

    return true;
  }

  @Implementation
  protected String blockingGetAuthToken(
      Account account, String authTokenType, boolean notifyAuthFailure) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }
    if (authTokenType == null) {
      throw new IllegalArgumentException("authTokenType is null");
    }

    Map<String, String> tokensForAccount = authTokens.get(account);
    if (tokensForAccount == null) {
      return null;
    }
    return tokensForAccount.get(authTokenType);
  }

  /**
   * The remove operation is posted to the given {@code handler}, and will be executed according to
   * the {@link IdleState} of the corresponding {@link org.robolectric.util.Scheduler}.
   */
  @Implementation
  protected AccountManagerFuture<Boolean> removeAccount(
      final Account account, AccountManagerCallback<Boolean> callback, Handler handler) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }

    return start(
        new BaseRoboAccountManagerFuture<Boolean>(callback, handler) {
          @Override
          public Boolean doWork()
              throws OperationCanceledException, IOException, AuthenticatorException {
            return removeAccountExplicitly(account);
          }
        });
  }

  /**
   * Removes the account unless {@link #setRemoveAccountIntent} has been set. If set, the future
   * Bundle will include the Intent and {@link AccountManager#KEY_BOOLEAN_RESULT} will be false.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected AccountManagerFuture<Bundle> removeAccount(
      Account account,
      Activity activity,
      AccountManagerCallback<Bundle> callback,
      Handler handler) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }
    return start(
        new BaseRoboAccountManagerFuture<Bundle>(callback, handler) {
          @Override
          public Bundle doWork()
              throws OperationCanceledException, IOException, AuthenticatorException {
            Bundle result = new Bundle();
            if (removeAccountIntent == null) {
              result.putBoolean(
                  AccountManager.KEY_BOOLEAN_RESULT, removeAccountExplicitly(account));
            } else {
              result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
              result.putParcelable(AccountManager.KEY_INTENT, removeAccountIntent);
            }
            return result;
          }
        });
  }

  @Implementation(minSdk = LOLLIPOP_MR1)
  protected boolean removeAccountExplicitly(Account account) {
    passwords.remove(account);
    userData.remove(account);
    if (accounts.remove(account)) {
      notifyListeners();
      return true;
    }
    return false;
  }

  /**
   * Removes all accounts that have been added.
   */
  public void removeAllAccounts() {
    passwords.clear();
    userData.clear();
    accounts.clear();
  }

  @Implementation
  protected AuthenticatorDescription[] getAuthenticatorTypes() {
    return authenticators.values().toArray(new AuthenticatorDescription[authenticators.size()]);
  }

  @Implementation
  protected void addOnAccountsUpdatedListener(
      final OnAccountsUpdateListener listener, Handler handler, boolean updateImmediately) {

    if (listeners.contains(listener)) {
      return;
    }

    listeners.add(listener);

    if (updateImmediately) {
      listener.onAccountsUpdated(getAccounts());
    }
  }

  @Implementation
  protected void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
    listeners.remove(listener);
  }

  @Implementation
  protected String getUserData(Account account, String key) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }

    if (!userData.containsKey(account)) {
      return null;
    }

    Map<String, String> userDataMap = userData.get(account);
    if (userDataMap.containsKey(key)) {
      return userDataMap.get(key);
    }

    return null;
  }

  @Implementation
  protected void setUserData(Account account, String key, String value) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }

    if (!userData.containsKey(account)) {
      userData.put(account, new HashMap<String, String>());
    }

    Map<String, String> userDataMap = userData.get(account);

    if (value == null) {
      userDataMap.remove(key);
    } else {
      userDataMap.put(key, value);
    }
  }

  @Implementation
  protected void setPassword(Account account, String password) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }

    if (password == null) {
      passwords.remove(account);
    } else {
      passwords.put(account, password);
    }
  }

  @Implementation
  protected String getPassword(Account account) {
    if (account == null) {
      throw new IllegalArgumentException("account is null");
    }

    if (passwords.containsKey(account)) {
      return passwords.get(account);
    } else {
      return null;
    }
  }

  @Implementation
  protected void invalidateAuthToken(final String accountType, final String authToken) {
    Account[] accountsByType = getAccountsByType(accountType);
    for (Account account : accountsByType) {
      Map<String, String> tokenMap = authTokens.get(account);
      if (tokenMap != null) {
        Iterator<Entry<String, String>> it = tokenMap.entrySet().iterator();
        while (it.hasNext()) {
          Map.Entry<String, String> map = it.next();
          if (map.getValue().equals(authToken)) {
            it.remove();
          }
        }
        authTokens.put(account, tokenMap);
      }
    }
  }

  private void notifyListeners() {
    Account[] accounts = getAccounts();
    Iterator<OnAccountsUpdateListener> iter = listeners.iterator();
    OnAccountsUpdateListener listener;
    while (iter.hasNext()) {
      listener = iter.next();
      listener.onAccountsUpdated(accounts);
    }
  }

  /**
   * @param account User account.
   */
  public void addAccount(Account account) {
    accounts.add(account);
    if (pendingAddFuture != null) {
      pendingAddFuture.resultBundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
      start(pendingAddFuture);
      pendingAddFuture = null;
    }
    notifyListeners();
  }

  /**
   * Adds an account to the AccountManager but when {@link AccountManager#getAccountsByTypeForPackage(String, String)}
   * is called will be included if is in one of the #visibileToPackages
   *
   * @param account User account.
   */
  public void addAccount(Account account, String... visibileToPackages) {
    addAccount(account);
    HashSet<String> value = new HashSet<>();
    Collections.addAll(value, visibileToPackages);
    packageVisibileAccounts.put(account, value);
  }

  /**
   * Consumes and returns the next {@code addAccountOptions} passed to {@link #addAccount}.
   *
   * @return the next {@code addAccountOptions}
   */
  public Bundle getNextAddAccountOptions() {
    if (addAccountOptionsList.isEmpty()) {
      return null;
    } else {
      return addAccountOptionsList.remove(0);
    }
  }

  /**
   * Returns the next {@code addAccountOptions} passed to {@link #addAccount} without consuming it.
   *
   * @return the next {@code addAccountOptions}
   */
  public Bundle peekNextAddAccountOptions() {
    if (addAccountOptionsList.isEmpty()) {
      return null;
    } else {
      return addAccountOptionsList.get(0);
    }
  }

  private class RoboAccountManagerFuture extends BaseRoboAccountManagerFuture<Bundle> {
    private final String accountType;
    private final Activity activity;
    private final Bundle resultBundle;

    RoboAccountManagerFuture(AccountManagerCallback<Bundle> callback, Handler handler, String accountType, Activity activity) {
      super(callback, handler);

      this.accountType = accountType;
      this.activity = activity;
      this.resultBundle = new Bundle();
    }

    @Override
    public Bundle doWork() throws OperationCanceledException, IOException, AuthenticatorException {
      if (!authenticators.containsKey(accountType)) {
        throw new AuthenticatorException("No authenticator specified for " + accountType);
      }

      resultBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);

      if (activity == null) {
        Intent resultIntent = new Intent();
        resultBundle.putParcelable(AccountManager.KEY_INTENT, resultIntent);
      } else if (callback == null) {
        resultBundle.putString(AccountManager.KEY_ACCOUNT_NAME, "some_user@gmail.com");
      }

      return resultBundle;
    }
  }

  @Implementation
  protected AccountManagerFuture<Bundle> addAccount(
      final String accountType,
      String authTokenType,
      String[] requiredFeatures,
      Bundle addAccountOptions,
      Activity activity,
      AccountManagerCallback<Bundle> callback,
      Handler handler) {
    addAccountOptionsList.add(addAccountOptions);
    pendingAddFuture = new RoboAccountManagerFuture(callback, handler, accountType, activity);
    return pendingAddFuture;
  }

  public void setFeatures(Account account, String[] accountFeatures) {
    HashSet<String> featureSet = new HashSet<>();
    featureSet.addAll(Arrays.asList(accountFeatures));
    this.accountFeatures.put(account, featureSet);
  }

  /**
   * @param authenticator System authenticator.
   */
  public void addAuthenticator(AuthenticatorDescription authenticator) {
    authenticators.put(authenticator.type, authenticator);
  }

  public void addAuthenticator(String type) {
    addAuthenticator(AuthenticatorDescription.newKey(type));
  }

  private Map<Account, String> previousNames = new HashMap<Account, String>();

  /**
   * Sets the previous name for an account, which will be returned by {@link AccountManager#getPreviousName(Account)}.
   *
   * @param account User account.
   * @param previousName Previous account name.
   */
  public void setPreviousAccountName(Account account, String previousName) {
    previousNames.put(account, previousName);
  }

  /** @see #setPreviousAccountName(Account, String) */
  @Implementation(minSdk = LOLLIPOP)
  protected String getPreviousName(Account account) {
    return previousNames.get(account);
  }

  @Implementation
  protected AccountManagerFuture<Bundle> getAuthToken(
      final Account account,
      final String authTokenType,
      final Bundle options,
      final Activity activity,
      final AccountManagerCallback<Bundle> callback,
      Handler handler) {

    return start(
        new BaseRoboAccountManagerFuture<Bundle>(callback, handler) {
          @Override
          public Bundle doWork()
              throws OperationCanceledException, IOException, AuthenticatorException {
            return getAuthToken(account, authTokenType);
          }
        });
  }

  @Implementation
  protected AccountManagerFuture<Bundle> getAuthToken(
      final Account account,
      final String authTokenType,
      final Bundle options,
      final boolean notifyAuthFailure,
      final AccountManagerCallback<Bundle> callback,
      Handler handler) {

    return start(new BaseRoboAccountManagerFuture<Bundle>(callback, handler) {
      @Override
      public Bundle doWork()
          throws OperationCanceledException, IOException, AuthenticatorException {
        return getAuthToken(account, authTokenType);
      }
    });
  }

  private Bundle getAuthToken(Account account, String authTokenType)
      throws OperationCanceledException, IOException, AuthenticatorException {
    Bundle result = new Bundle();

    String authToken = blockingGetAuthToken(account, authTokenType, false);
    result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
    result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    result.putString(AccountManager.KEY_AUTHTOKEN, authToken);

    if (authToken != null) {
      return result;
    }

    if (!authenticators.containsKey(account.type)) {
      throw new AuthenticatorException("No authenticator specified for " + account.type);
    }

    Intent resultIntent = new Intent();
    result.putParcelable(AccountManager.KEY_INTENT, resultIntent);

    return result;
  }

  @Implementation
  protected AccountManagerFuture<Boolean> hasFeatures(
      final Account account,
      final String[] features,
      AccountManagerCallback<Boolean> callback,
      Handler handler) {
    return start(new BaseRoboAccountManagerFuture<Boolean>(callback, handler) {
      @Override
      public Boolean doWork() throws OperationCanceledException, IOException, AuthenticatorException {
        Set<String> availableFeatures = accountFeatures.get(account);
        for (String feature : features) {
          if (!availableFeatures.contains(feature)) {
            return false;
          }
        }
        return true;
      }
    });
  }

  @Implementation
  protected AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
      final String type,
      final String[] features,
      AccountManagerCallback<Account[]> callback,
      Handler handler) {
    return start(
        new BaseRoboAccountManagerFuture<Account[]>(callback, handler) {
          @Override
          public Account[] doWork()
              throws OperationCanceledException, IOException, AuthenticatorException {

            if (authenticationErrorOnNextResponse) {
              setAuthenticationErrorOnNextResponse(false);
              throw new AuthenticatorException();
            }

            List<Account> result = new ArrayList<>();

            Account[] accountsByType = getAccountsByType(type);
            for (Account account : accountsByType) {
              Set<String> featureSet = accountFeatures.get(account);
              if (features == null || featureSet.containsAll(Arrays.asList(features))) {
                result.add(account);
              }
            }
            return result.toArray(new Account[result.size()]);
          }
        });
  }

  private <T extends BaseRoboAccountManagerFuture> T start(T future) {
    future.start();
    return future;
  }

  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected Account[] getAccountsByTypeForPackage(String type, String packageName) {
    List<Account> result = new ArrayList<>();

    Account[] accountsByType = getAccountsByType(type);
    for (Account account : accountsByType) {
      if (packageVisibileAccounts.containsKey(account) && packageVisibileAccounts.get(account).contains(packageName)) {
        result.add(account);
      }
    }

    return result.toArray(new Account[result.size()]);
  }

  /**
   * Sets authenticator exception, which will be thrown by {@link #getAccountsByTypeAndFeatures}.
   *
   * @param authenticationErrorOnNextResponse to set flag that exception will be thrown on next
   *     response.
   */
  public void setAuthenticationErrorOnNextResponse(boolean authenticationErrorOnNextResponse) {
    this.authenticationErrorOnNextResponse = authenticationErrorOnNextResponse;
  }

  /**
   * Sets the intent to include in Bundle result from {@link #removeAccount} if Activity is given.
   *
   * @param removeAccountIntent the intent to surface as {@link AccountManager#KEY_INTENT}.
   */
  public void setRemoveAccountIntent(Intent removeAccountIntent) {
    this.removeAccountIntent = removeAccountIntent;
  }

  private abstract class BaseRoboAccountManagerFuture<T> implements AccountManagerFuture<T> {
    protected final AccountManagerCallback<T> callback;
    private final Handler handler;
    protected T result;
    private Exception exception;
    private boolean started = false;

    BaseRoboAccountManagerFuture(AccountManagerCallback<T> callback, Handler handler) {
      this.callback = callback;
      this.handler = handler == null ? mainHandler : handler;
    }

    void start() {
      if (started) return;
      started = true;

      try {
        result = doWork();
      } catch (OperationCanceledException | IOException | AuthenticatorException e) {
        exception = e;
      }

      if (callback != null) {
        handler.post(
            new Runnable() {
              @Override
              public void run() {
                callback.run(BaseRoboAccountManagerFuture.this);
              }
            });
      }
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
      return false;
    }

    @Override
    public boolean isCancelled() {
      return false;
    }

    @Override
    public boolean isDone() {
      return result != null || exception != null || isCancelled();
    }

    @Override
    public T getResult() throws OperationCanceledException, IOException, AuthenticatorException {
      start();

      if (exception instanceof OperationCanceledException) {
        throw new OperationCanceledException(exception);
      } else if (exception instanceof IOException) {
        throw new IOException(exception);
      } else if (exception instanceof AuthenticatorException) {
        throw new AuthenticatorException(exception);
      }
      return result;
    }

    @Override
    public T getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException, AuthenticatorException {
      return getResult();
    }

    public abstract T doWork() throws OperationCanceledException, IOException, AuthenticatorException;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.KITKAT;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.app.ResourcesManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;

@Implements(value = ResourcesManager.class, isInAndroidSdk = false, minSdk = KITKAT)
public class ShadowResourcesManager {
  @RealObject ResourcesManager realResourcesManager;

  @Resetter
  public static void reset() {
    reflector(_ResourcesManager_.class).setResourcesManager(null);
  }

  /**
   * Exposes {@link ResourcesManager#applyCompatConfigurationLocked(int, Configuration)}.
   */
  public boolean callApplyConfigurationToResourcesLocked(Configuration configuration,
      CompatibilityInfo compatibilityInfo) {
    return reflector(_ResourcesManager_.class, realResourcesManager)
        .applyConfigurationToResourcesLocked(configuration, compatibilityInfo);
  }

  /** Accessor interface for {@link ResourcesManager}'s internals. */
  @ForType(ResourcesManager.class)
  private interface _ResourcesManager_ {
    boolean applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat);

    @Static @Accessor("sResourcesManager")
    void setResourcesManager(ResourcesManager resourcesManager);
  }
}


package org.robolectric.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * Generic collection of utility methods.
 */
public class Util {

  /**
   * Returns the Java version as an int value.
   *
   * @return the Java version as an int value (8, 9, etc.)
   */
  public static int getJavaVersion() {
    String version = System.getProperty("java.version");
    assert version != null;
    if (version.startsWith("1.")) {
      version = version.substring(2);
    }
    // Allow these formats:
    // 1.8.0_72-ea
    // 9-ea
    // 9
    // 9.0.1
    int dotPos = version.indexOf('.');
    int dashPos = version.indexOf('-');
    return Integer.parseInt(
        version.substring(0, dotPos > -1 ? dotPos : dashPos > -1 ? dashPos : 1));
  }

  public static void copy(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[8196];
    int len;
    try {
      while ((len = in.read(buffer)) != -1) {
        out.write(buffer, 0, len);
      }
    } finally {
      in.close();
    }
  }

  /**
   * This method consumes an input stream and returns its content.
   *
   * @param is The input stream to read from.
   * @return The bytes read from the stream.
   * @throws IOException Error reading from stream.
   */
  public static byte[] readBytes(InputStream is) throws IOException {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream(is.available())) {
      copy(is, bos);
      return bos.toByteArray();
    }
  }

  public static <T> T[] reverse(T[] array) {
    for (int i = 0; i < array.length / 2; i++) {
      int destI = array.length - i - 1;
      T o = array[destI];
      array[destI] = array[i];
      array[i] = o;
    }
    return array;
  }

  public static File file(String... pathParts) {
    return file(new File("."), pathParts);
  }

  public static File file(File f, String... pathParts) {
    for (String pathPart : pathParts) {
      f = new File(f, pathPart);
    }

    String dotSlash = "." + File.separator;
    if (f.getPath().startsWith(dotSlash)) {
      f = new File(f.getPath().substring(dotSlash.length()));
    }

    return f;
  }

  @SuppressWarnings("NewApi")
  public static Path pathFrom(URL localArtifactUrl) {
    try {
      return Paths.get(localArtifactUrl.toURI());
    } catch (URISyntaxException e) {
      throw new RuntimeException("huh? " + localArtifactUrl, e);
    }
  }

  public static List<Integer> intArrayToList(int[] ints) {
    List<Integer> youSuckJava = new ArrayList<>();
    for (int attr1 : ints) {
      youSuckJava.add(attr1);
    }
    return youSuckJava;
  }

  public static int parseInt(String valueFor) {
    if (valueFor.startsWith("0x")) {
      return Integer.parseInt(valueFor.substring(2), 16);
    } else {
      return Integer.parseInt(valueFor, 10);
    }
  }

  /**
   * Re-throw {@code t} (even if it's a checked exception) without requiring a {@code throws}
   * declaration.
   *

   * This function declares a return type of {@link RuntimeException} but will never actually return
   * a value. This allows you to use it with a {@code throw} statement to convince the compiler that
   * the current branch will not complete.
   *
{@code
   * throw Util.sneakyThrow(new IOException());
   * }

   *

   * Adapted from https://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
   */
  @SuppressWarnings("unchecked")
  public static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
    throw (T) t;
  }
}


package org.robolectric.shadows;

import android.annotation.NonNull;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Build.VERSION_CODES;
import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

@Implements(value = CameraManager.class, minSdk = VERSION_CODES.LOLLIPOP)
public class ShadowCameraManager {

  // LinkedHashMap used to ensure getCameraIdList returns ids in the order in which they were added
  private final Map<String, CameraCharacteristics> cameraIdToCharacteristics =
      new LinkedHashMap<>();
  private final Map<String, Boolean> cameraTorches = new HashMap<>();

  @Implementation
  @NonNull
  protected String[] getCameraIdList() throws CameraAccessException {
    Set<String> cameraIds = cameraIdToCharacteristics.keySet();
    return cameraIds.toArray(new String[0]);
  }

  @Implementation
  @NonNull
  protected CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId) {
    Preconditions.checkNotNull(cameraId);
    CameraCharacteristics characteristics = cameraIdToCharacteristics.get(cameraId);
    Preconditions.checkArgument(characteristics != null);
    return characteristics;
  }

  @Implementation(minSdk = VERSION_CODES.M)
  protected void setTorchMode(@NonNull String cameraId, boolean enabled) {
    Preconditions.checkNotNull(cameraId);
    Preconditions.checkArgument(cameraIdToCharacteristics.keySet().contains(cameraId));
    cameraTorches.put(cameraId, enabled);
  }

  /**
   * Adds the given cameraId and characteristics to this shadow.
   *
   * <p>The result from {@link #getCameraIdList()} will be in the order in which cameras were added.
   *
   * @throws IllegalArgumentException if there's already an existing camera with the given id.
   */
  public void addCamera(@NonNull String cameraId, @NonNull CameraCharacteristics characteristics) {
    Preconditions.checkNotNull(cameraId);
    Preconditions.checkNotNull(characteristics);
    Preconditions.checkArgument(!cameraIdToCharacteristics.containsKey(cameraId));

    cameraIdToCharacteristics.put(cameraId, characteristics);
  }

  /** Returns what the supplied camera's torch is set to. */
  public boolean getTorchMode(@NonNull String cameraId) {
    Preconditions.checkNotNull(cameraId);
    Preconditions.checkArgument(cameraIdToCharacteristics.keySet().contains(cameraId));
    Boolean torchState = cameraTorches.get(cameraId);
    return torchState;
  }
}


package org.robolectric.res.android;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A unique id per object registry. Used to emulate android platform behavior of storing a long
 * which represents a pointer to an object.
 */
public class NativeObjRegistry<T> {

  private static final int INITIAL_ID = 1;

  private final String name;
  private final boolean debug;
  private final BiMap<Long, T> nativeObjToIdMap = HashBiMap.create();
  private final Map<Long, DebugInfo> idToDebugInfoMap;

  private long nextId = INITIAL_ID;

  public NativeObjRegistry(Class<T> theClass) {
    this(theClass, false);
  }

  public NativeObjRegistry(Class<T> theClass, boolean debug) {
    this(theClass.getSimpleName(), debug);
  }

  public NativeObjRegistry(String name) {
    this(name, false);
  }

  public NativeObjRegistry(String name, boolean debug) {
    this.name = name;
    this.debug = debug;
    this.idToDebugInfoMap = debug ? new HashMap<>() : null;
  }

  /**
   * Retrieve the native id for given object. Assigns a new unique id to the object if not
   * previously registered.
   *
   * @deprecated Use {@link #register(Object)} instead.
   */
  @Deprecated
  public synchronized long getNativeObjectId(T o) {
    checkNotNull(o);
    Long nativeId = nativeObjToIdMap.inverse().get(o);
    if (nativeId == null) {
      nativeId = nextId;
      if (debug) {
        System.out.printf("NativeObjRegistry %s: register %d -> %s%n", name, nativeId, o);
      }
      nativeObjToIdMap.put(nativeId, o);
      nextId++;
    }
    return nativeId;
  }

  /**
   * Register and assign a new unique native id for given object (representing a C memory pointer).
   *
   * @throws IllegalStateException if the object was previously registered
   */
  public synchronized long register(T o) {
    checkNotNull(o);
    Long nativeId = nativeObjToIdMap.inverse().get(o);
    if (nativeId != null) {
      if (debug) {
        DebugInfo debugInfo = idToDebugInfoMap.get(nativeId);
        if (debugInfo != null) {
          System.out.printf(
              "NativeObjRegistry %s: register %d -> %s already registered:%n", name, nativeId, o);
          debugInfo.registrationTrace.printStackTrace(System.out);
        }
      }
      throw new IllegalStateException("Object was previously registered with id " + nativeId);
    }

    nativeId = nextId;
    if (debug) {
      System.out.printf("NativeObjRegistry %s: register %d -> %s%n", name, nativeId, o);
      idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace(o)));
    }
    nativeObjToIdMap.put(nativeId, o);
    nextId++;
    return nativeId;
  }

  /**
   * Unregister an object previously registered with {@link #register(Object)}.
   *
   * @param nativeId the unique id (representing a C memory pointer) of the object to unregister.
   * @throws IllegalStateException if the object was never registered, or was previously
   *     unregistered.
   */
  public synchronized T unregister(long nativeId) {
    T o = nativeObjToIdMap.remove(nativeId);
    if (debug) {
      System.out.printf("NativeObjRegistry %s: unregister %d -> %s%n", name, nativeId, o);
      new RuntimeException("unregister debug").printStackTrace(System.out);
    }
    if (o == null) {
      if (debug) {
        DebugInfo debugInfo = idToDebugInfoMap.get(nativeId);
        debugInfo.unregistrationTraces.add(new Trace(o));
        if (debugInfo.unregistrationTraces.size() > 1) {
          System.out.format("NativeObjRegistry %s: Too many unregistrations:%n", name);
          for (Trace unregistration : debugInfo.unregistrationTraces) {
            unregistration.printStackTrace(System.out);
          }
        }
      }
      throw new IllegalStateException(
          nativeId + " has already been removed (or was never registered)");
    }
    return o;
  }

  /**
   * @deprecated Use {@link #unregister(long)} instead.
   */
  @Deprecated
  public synchronized void unregister(T removed) {
    nativeObjToIdMap.inverse().remove(removed);
  }

  /** Retrieve the native object for given id. Throws if object with that id cannot be found */
  public synchronized T getNativeObject(long nativeId) {
    T object = nativeObjToIdMap.get(nativeId);
    if (object != null) {
      return object;
    } else {
      throw new NullPointerException(
          String.format(
              "Could not find object with nativeId: %d. Currently registered ids: %s",
              nativeId, nativeObjToIdMap.keySet()));
    }
  }

  /**
   * Similar to {@link #getNativeObject(long)} but returns null if object with given id cannot be
   * found.
   */
  public synchronized T peekNativeObject(long nativeId) {
    return nativeObjToIdMap.get(nativeId);
  }

  /** WARNING -- dangerous! Call {@link #unregister(long)} instead! */
  public synchronized void clear() {
    nextId = INITIAL_ID;
    nativeObjToIdMap.clear();
  }

  private static class DebugInfo {
    final Trace registrationTrace;
    final List<Trace> unregistrationTraces = new ArrayList<>();

    public DebugInfo(Trace trace) {
      registrationTrace = trace;
    }
  }

  private static class Trace extends Throwable {
    private int apiLevel;
    private boolean useLegacyResources;

    private Trace(Object o) {
      try {
        Class<?> runtimeEnvClass = o.getClass().getClassLoader()
            .loadClass("org.robolectric.RuntimeEnvironment");
        this.apiLevel = (int) runtimeEnvClass.getMethod("getApiLevel").invoke(null);
        this.useLegacyResources = (boolean) runtimeEnvClass.getMethod("useLegacyResources")
            .invoke(null);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    private Trace(int apiLevel, boolean legacyResources) {
    }
  }
}