package org.robolectric.util;

import static org.robolectric.util.Scheduler.IdleState.CONSTANT_IDLE;
import static org.robolectric.util.Scheduler.IdleState.PAUSED;
import static org.robolectric.util.Scheduler.IdleState.UNPAUSED;

import java.time.Duration;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.concurrent.TimeUnit;

/**
 * Class that manages a queue of Runnables that are scheduled to run now (or at some time in the
 * future). Runnables that are scheduled to run on the UI thread (tasks, animations, etc) eventually
 * get routed to a Scheduler instance.
 *
 *
The execution of a scheduler can be in one of three states:
 *
 *

 *
paused ({@link #pause()}): if paused, then no posted events will be run unless the
 *       Scheduler is explicitly instructed to do so, correctly matching Android's behavior.
 *
normal ({@link #unPause()}): if not paused but not set to idle constantly, then the
 *       Scheduler will automatically run any {@link Runnable}s that are scheduled to run at or
 *       before the Scheduler's current time, but it won't automatically run any future events. To
 *       run future events the Scheduler needs to have its clock advanced.
 *
idling constantly: if {@link #idleConstantly(boolean)} is called with <tt>true</tt>, then
 *       the Scheduler will continue looping through posted events (including future events),
 *       advancing its clock as it goes.
 *

 */
public class Scheduler {

  /**
   * Describes the current state of a {@link Scheduler}.
   */
  public enum IdleState {
    /**
     * The <tt>Scheduler</tt> will not automatically advance the clock nor execute any runnables.
     */
    PAUSED,
    /**
     * The <tt>Scheduler</tt>'s clock won't automatically advance the clock but will automatically
     * execute any runnables scheduled to execute at or before the current time.
     */
    UNPAUSED,
    /**
     * The <tt>Scheduler</tt> will automatically execute any runnables (past, present or future)
     * as soon as they are posted and advance the clock if necessary.
     */
    CONSTANT_IDLE
  }

  private static final long START_TIME = 100;
  private volatile long currentTime = START_TIME;
  /**
   * PriorityQueue doesn't maintain ordering based on insertion; track that ourselves to preserve
   * FIFO order for posted runnables with the same scheduled time.
   */
  private long nextTimeDisambiguator = 0;

  private boolean isExecutingRunnable = false;
  private final Thread associatedThread = Thread.currentThread();
  private final PriorityQueue<ScheduledRunnable> runnables = new PriorityQueue<>();
  private volatile IdleState idleState = UNPAUSED;

  /**
   * Retrieves the current idling state of this <tt>Scheduler</tt>.
   * @return The current idle state of this <tt>Scheduler</tt>.
   * @see #setIdleState(IdleState)
   * @see #isPaused()
   */
  public IdleState getIdleState() {
    return idleState;
  }

  /**
   * Sets the current idling state of this <tt>Scheduler</tt>. If transitioning to the
   * {@link IdleState#UNPAUSED} state any tasks scheduled to be run at or before the current time
   * will be run, and if transitioning to the {@link IdleState#CONSTANT_IDLE} state all scheduled
   * tasks will be run and the clock advanced to the time of the last runnable.
   * @param idleState The new idle state of this <tt>Scheduler</tt>.
   * @see #setIdleState(IdleState)
   * @see #isPaused()
   */
  public synchronized void setIdleState(IdleState idleState) {
    this.idleState = idleState;
    switch (idleState) {
      case UNPAUSED:
        advanceBy(0);
        break;
      case CONSTANT_IDLE:
        advanceToLastPostedRunnable();
        break;
      default:
    }
  }

  /**
   * Get the current time (as seen by the scheduler), in milliseconds.
   *
   * @return  Current time in milliseconds.
   */
  public long getCurrentTime() {
    return currentTime;
  }

  /**
   * Pause the scheduler. Equivalent to <tt>setIdleState(PAUSED)</tt>.
   *
   * @see #unPause()
   * @see #setIdleState(IdleState)
   */
  public synchronized void pause() {
    setIdleState(PAUSED);
  }

  /**
   * Un-pause the scheduler. Equivalent to <tt>setIdleState(UNPAUSED)</tt>.
   *
   * @see #pause()
   * @see #setIdleState(IdleState)
   */
  public synchronized void unPause() {
    setIdleState(UNPAUSED);
  }

  /**
   * Determine if the scheduler is paused.
   *
   * @return  <tt>true</tt> if it is paused.
   */
  public boolean isPaused() {
    return idleState == PAUSED;
  }

  /**
   * Add a runnable to the queue.
   *
   * @param runnable    Runnable to add.
   */
  public synchronized void post(Runnable runnable) {
    postDelayed(runnable, 0, TimeUnit.MILLISECONDS);
  }

  /**
   * Add a runnable to the queue to be run after a delay.
   *
   * @param runnable    Runnable to add.
   * @param delayMillis Delay in millis.
   */
  public synchronized void postDelayed(Runnable runnable, long delayMillis) {
    postDelayed(runnable, delayMillis, TimeUnit.MILLISECONDS);
  }

  /**
   * Add a runnable to the queue to be run after a delay.
   */
  public synchronized void postDelayed(Runnable runnable, long delay, TimeUnit unit) {
    long delayMillis = unit.toMillis(delay);
    if ((idleState != CONSTANT_IDLE && (isPaused() || delayMillis > 0)) || Thread.currentThread() != associatedThread) {
      runnables.add(new ScheduledRunnable(runnable, currentTime + delayMillis));
    } else {
      runOrQueueRunnable(runnable, currentTime + delayMillis);
    }
  }

  /**
   * Add a runnable to the head of the queue.
   *
   * @param runnable  Runnable to add.
   */
  public synchronized void postAtFrontOfQueue(Runnable runnable) {
    if (isPaused() || Thread.currentThread() != associatedThread) {
      final long timeDisambiguator;
      if (runnables.isEmpty()) {
        timeDisambiguator = nextTimeDisambiguator++;
      } else {
        timeDisambiguator = runnables.peek().timeDisambiguator - 1;
      }
      runnables.add(new ScheduledRunnable(runnable, 0, timeDisambiguator));
    } else {
      runOrQueueRunnable(runnable, currentTime);
    }
  }

  /**
   * Remove a runnable from the queue.
   *
   * @param runnable  Runnable to remove.
   */
  public synchronized void remove(Runnable runnable) {
    Iterator<ScheduledRunnable> iterator = runnables.iterator();
    while (iterator.hasNext()) {
      if (iterator.next().runnable == runnable) {
        iterator.remove();
      }
    }
  }

  /**
   * Run all runnables in the queue, and any additional runnables they schedule that are scheduled
   * before the latest scheduled runnable currently in the queue.
   *
   * @return True if a runnable was executed.
   */
  public synchronized boolean advanceToLastPostedRunnable() {
    long currentMaxTime = currentTime;
    for (ScheduledRunnable scheduled : runnables) {
      if (currentMaxTime < scheduled.scheduledTime) {
        currentMaxTime = scheduled.scheduledTime;
      }
    }
    return advanceTo(currentMaxTime);
  }

  /**
   * Run the next runnable in the queue.
   *
   * @return  True if a runnable was executed.
   */
  public synchronized boolean advanceToNextPostedRunnable() {
    return !runnables.isEmpty() && advanceTo(runnables.peek().scheduledTime);
  }

  /**
   * Run all runnables that are scheduled to run in the next time interval.
   *
   * @param   interval  Time interval (in millis).
   * @return  True if a runnable was executed.
   * @deprecated Use {@link #advanceBy(long, TimeUnit)}.
   */
  @Deprecated
  public synchronized boolean advanceBy(long interval) {
    return advanceBy(interval, TimeUnit.MILLISECONDS);
  }

  /**
   * Run all runnables that are scheduled to run in the next time interval.
   *
   * @return  True if a runnable was executed.
   */
  public synchronized boolean advanceBy(long amount, TimeUnit unit) {
    long endingTime = currentTime + unit.toMillis(amount);
    return advanceTo(endingTime);
  }

  /**
   * Run all runnables that are scheduled before the endTime.
   *
   * @param   endTime   Future time.
   * @return  True if a runnable was executed.
   */
  public synchronized boolean advanceTo(long endTime) {
    if (endTime < currentTime || runnables.isEmpty()) {
      currentTime = endTime;
      return false;
    }

    int runCount = 0;
    while (nextTaskIsScheduledBefore(endTime)) {
      runOneTask();
      ++runCount;
    }
    currentTime = endTime;
    return runCount > 0;
  }

  /**
   * Run the next runnable in the queue.
   *
   * @return  True if a runnable was executed.
   */
  public synchronized boolean runOneTask() {
    ScheduledRunnable postedRunnable = runnables.poll();
    if (postedRunnable != null) {
      if (postedRunnable.scheduledTime > currentTime) {
        currentTime = postedRunnable.scheduledTime;
      }
      postedRunnable.run();
      return true;
    }
    return false;
  }

  /**
   * Determine if any enqueued runnables are enqueued before the current time.
   *
   * @return  True if any runnables can be executed.
   */
  public synchronized boolean areAnyRunnable() {
    return nextTaskIsScheduledBefore(currentTime);
  }

  /**
   * Reset the internal state of the Scheduler.
   */
  public synchronized void reset() {
    runnables.clear();
    idleState = UNPAUSED;
    currentTime = START_TIME;
    isExecutingRunnable = false;
  }

  /**
   * Return the number of enqueued runnables.
   *
   * @return  Number of enqueues runnables.
   */
  public synchronized int size() {
    return runnables.size();
  }

  @SuppressWarnings("AndroidJdkLibsChecker")
  public synchronized Duration getNextScheduledTaskTime() {
    return runnables.isEmpty() ? Duration.ZERO : Duration.ofMillis(runnables.peek().scheduledTime);
  }

  @SuppressWarnings("AndroidJdkLibsChecker")
  public synchronized Duration getLastScheduledTaskTime() {
    if (runnables.isEmpty()) {
      return Duration.ZERO;
    }
    long currentMaxTime = currentTime;
    for (ScheduledRunnable scheduled : runnables) {
      if (currentMaxTime < scheduled.scheduledTime) {
        currentMaxTime = scheduled.scheduledTime;
      }

    }
    return Duration.ofMillis(currentMaxTime);
  }

  /**
   * Set the idle state of the Scheduler. If necessary, the clock will be advanced and runnables
   * executed as required by the newly-set state.
   *
   * @param shouldIdleConstantly  If <tt>true</tt> the idle state will be set to
   *                              {@link IdleState#CONSTANT_IDLE}, otherwise it will be set to
   *                              {@link IdleState#UNPAUSED}.
   * @deprecated This method is ambiguous in how it should behave when turning off constant idle.
   * Use {@link #setIdleState(IdleState)} instead to explicitly set the state.
   */
  @Deprecated
  public void idleConstantly(boolean shouldIdleConstantly) {
    setIdleState(shouldIdleConstantly ? CONSTANT_IDLE : UNPAUSED);
  }

  private boolean nextTaskIsScheduledBefore(long endingTime) {
    return !runnables.isEmpty() && runnables.peek().scheduledTime <= endingTime;
  }

  private void runOrQueueRunnable(Runnable runnable, long scheduledTime) {
    if (isExecutingRunnable) {
      runnables.add(new ScheduledRunnable(runnable, scheduledTime));
      return;
    }
    isExecutingRunnable = true;
    try {
      runnable.run();
    } finally {
      isExecutingRunnable = false;
    }
    if (scheduledTime > currentTime) {
      currentTime = scheduledTime;
    }
    // The runnable we just ran may have queued other runnables. If there are
    // any pending immediate execution we should run these now too, unless we are
    // paused.
    switch (idleState) {
      case CONSTANT_IDLE:
        advanceToLastPostedRunnable();
        break;
      case UNPAUSED:
        advanceBy(0);
        break;
      default:
    }
  }

  private class ScheduledRunnable implements Comparable<ScheduledRunnable> {
    private final Runnable runnable;
    private final long scheduledTime;
    private final long timeDisambiguator;

    private ScheduledRunnable(Runnable runnable, long scheduledTime) {
      this(runnable, scheduledTime, nextTimeDisambiguator++);
    }

    private ScheduledRunnable(Runnable runnable, long scheduledTime, long timeDisambiguator) {
      this.runnable = runnable;
      this.scheduledTime = scheduledTime;
      this.timeDisambiguator = timeDisambiguator;
    }

    @Override
    public int compareTo(ScheduledRunnable runnable) {
      int timeCompare = Long.compare(scheduledTime, runnable.scheduledTime);
      if (timeCompare == 0) {
        return Long.compare(timeDisambiguator, runnable.timeDisambiguator);
      }
      return timeCompare;
    }

    public void run() {
      isExecutingRunnable = true;
      try {
        runnable.run();
      } finally {
        isExecutingRunnable = false;
      }
    }
  }
}


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;
import static android.os.Build.VERSION_CODES.O;
import static org.robolectric.shadow.api.Shadow.directlyOn;

import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.ServiceConnection;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.UserHandle;
import java.io.File;
import java.nio.file.Paths;
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.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;

@Implements(className = ShadowContextImpl.CLASS_NAME)
@SuppressWarnings("NewApi")
public class ShadowContextImpl {

  public static final String CLASS_NAME = "android.app.ContextImpl";
  private ContentResolver contentResolver;

  @RealObject private Context realContextImpl;

  private Map<String, Object> systemServices = new HashMap<String, Object>();
  private final Set<String> removedSystemServices = new HashSet<>();

  /**
   * Returns the handle to a system-level service by name. If the service is not available in
   * Roboletric, or it is set to unavailable in {@link ShadowServiceManager#setServiceAvailability},
   * {@code null} will be returned.
   */
  @Implementation
  @Nullable
  protected Object getSystemService(String name) {
    if (removedSystemServices.contains(name)) {
      return null;
    }
    if (!systemServices.containsKey(name)) {
      return directlyOn(
          realContextImpl,
          ShadowContextImpl.CLASS_NAME,
          "getSystemService",
          ClassParameter.from(String.class, name));
    }
    return systemServices.get(name);
  }

  public void setSystemService(String key, Object service) {
    systemServices.put(key, service);
  }

  /**
   * Makes {@link #getSystemService(String)} return {@code null} for the given system service name,
   * mimicking a device that doesn't have that system service.
   */
  public void removeSystemService(String name) {
    removedSystemServices.add(name);
  }

  @Implementation
  protected void startIntentSender(
      IntentSender intent,
      Intent fillInIntent,
      int flagsMask,
      int flagsValues,
      int extraFlags,
      Bundle options)
      throws IntentSender.SendIntentException {
    intent.sendIntent(realContextImpl, 0, fillInIntent, null, null, null);
  }

  @Implementation
  protected ClassLoader getClassLoader() {
    return this.getClass().getClassLoader();
  }

  @Implementation
  protected int checkCallingPermission(String permission) {
    return checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid());
  }

  @Implementation
  protected int checkCallingOrSelfPermission(String permission) {
    return checkCallingPermission(permission);
  }

  @Implementation
  protected ContentResolver getContentResolver() {
    if (contentResolver == null) {
      contentResolver =
          new ContentResolver(realContextImpl) {
            @Override
            protected IContentProvider acquireProvider(Context c, String name) {
              return null;
            }

            @Override
            public boolean releaseProvider(IContentProvider icp) {
              return false;
            }

            @Override
            protected IContentProvider acquireUnstableProvider(Context c, String name) {
              return null;
            }

            @Override
            public boolean releaseUnstableProvider(IContentProvider icp) {
              return false;
            }

            @Override
            public void unstableProviderDied(IContentProvider icp) {}
          };
    }
    return contentResolver;
  }

  @Implementation
  protected void sendBroadcast(Intent intent) {
    getShadowInstrumentation().sendBroadcastWithPermission(intent, null, realContextImpl);
  }

  @Implementation
  protected void sendBroadcast(Intent intent, String receiverPermission) {
    getShadowInstrumentation()
        .sendBroadcastWithPermission(intent, receiverPermission, realContextImpl);
  }

  /** Forwards the call to {@link #sendBroadcast(Intent)}, disregarding {@code user} param. */
  @Implementation(minSdk = JELLY_BEAN_MR1)
  @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
  protected void sendBroadcastAsUser(@RequiresPermission Intent intent, UserHandle user) {
    sendBroadcast(intent);
  }

  /**
   * Forwards the call to {@link #sendBroadcast(Intent,String)}, disregarding {@code user} param.
   */
  @Implementation(minSdk = JELLY_BEAN_MR1)
  @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
  protected void sendBroadcastAsUser(
      @RequiresPermission Intent intent, UserHandle user, @Nullable String receiverPermission) {
    sendBroadcast(intent, receiverPermission);
  }

  @Implementation
  protected void sendOrderedBroadcast(Intent intent, String receiverPermission) {
    getShadowInstrumentation()
        .sendOrderedBroadcastWithPermission(intent, receiverPermission, realContextImpl);
  }

  @Implementation
  protected void sendOrderedBroadcast(
      Intent intent,
      String receiverPermission,
      BroadcastReceiver resultReceiver,
      Handler scheduler,
      int initialCode,
      String initialData,
      Bundle initialExtras) {
    getShadowInstrumentation()
        .sendOrderedBroadcast(
            intent,
            receiverPermission,
            resultReceiver,
            scheduler,
            initialCode,
            initialData,
            initialExtras,
            realContextImpl);
  }

  /** Behaves as {@link #sendOrderedBroadcast} and currently ignores userHandle. */
  @Implementation(minSdk = KITKAT)
  protected void sendOrderedBroadcastAsUser(
      Intent intent,
      UserHandle userHandle,
      String receiverPermission,
      BroadcastReceiver resultReceiver,
      Handler scheduler,
      int initialCode,
      String initialData,
      Bundle initialExtras) {
    sendOrderedBroadcast(
        intent,
        receiverPermission,
        resultReceiver,
        scheduler,
        initialCode,
        initialData,
        initialExtras
    );
  }

  /** Behaves as {@link #sendOrderedBroadcast}. Currently ignores userHandle, appOp, and options. */
  @Implementation(minSdk = M)
  protected void sendOrderedBroadcastAsUser(
      Intent intent,
      UserHandle userHandle,
      String receiverPermission,
      int appOp,
      Bundle options,
      BroadcastReceiver resultReceiver,
      Handler scheduler,
      int initialCode,
      String initialData,
      Bundle initialExtras) {
    sendOrderedBroadcast(
        intent,
        receiverPermission,
        resultReceiver,
        scheduler,
        initialCode,
        initialData,
        initialExtras
    );
  }

  @Implementation
  protected void sendStickyBroadcast(Intent intent) {
    getShadowInstrumentation().sendStickyBroadcast(intent, realContextImpl);
  }

  @Implementation
  protected int checkPermission(String permission, int pid, int uid) {
    return getShadowInstrumentation().checkPermission(permission, pid, uid);
  }

  @Implementation
  protected Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    return getShadowInstrumentation().registerReceiver(receiver, filter, realContextImpl);
  }

  @Implementation
  protected Intent registerReceiver(
      BroadcastReceiver receiver,
      IntentFilter filter,
      String broadcastPermission,
      Handler scheduler) {
    return getShadowInstrumentation()
        .registerReceiver(receiver, filter, broadcastPermission, scheduler, realContextImpl);
  }

  @Implementation(minSdk = JELLY_BEAN_MR1)
  protected Intent registerReceiverAsUser(
      BroadcastReceiver receiver,
      UserHandle user,
      IntentFilter filter,
      String broadcastPermission,
      Handler scheduler) {
    return getShadowInstrumentation()
        .registerReceiverWithContext(
            receiver, filter, broadcastPermission, scheduler, realContextImpl);
  }

  @Implementation
  protected void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
    getShadowInstrumentation().unregisterReceiver(broadcastReceiver);
  }

  @Implementation
  protected ComponentName startService(Intent service) {
    return getShadowInstrumentation().startService(service);
  }

  @Implementation(minSdk = O)
  protected ComponentName startForegroundService(Intent service) {
    return getShadowInstrumentation().startService(service);
  }

  @Implementation
  protected boolean stopService(Intent name) {
    return getShadowInstrumentation().stopService(name);
  }

  @Implementation
  protected boolean bindService(Intent intent, final ServiceConnection serviceConnection, int i) {
    return getShadowInstrumentation().bindService(intent, serviceConnection, i);
  }

  /** Binds to a service but ignores the given UserHandle. */
  @Implementation(minSdk = LOLLIPOP)
  protected boolean bindServiceAsUser(
      Intent intent, final ServiceConnection serviceConnection, int i, UserHandle userHandle) {
    return getShadowInstrumentation().bindService(intent, serviceConnection, i);
  }

  @Implementation
  protected void unbindService(final ServiceConnection serviceConnection) {
    getShadowInstrumentation().unbindService(serviceConnection);
  }

  /**
   * Behaves as {@link #startActivity}. The user parameter is ignored.
   */
  @Implementation(minSdk = LOLLIPOP)
  protected void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
    // TODO: Remove this once {@link com.android.server.wmActivityTaskManagerService} is
    // properly shadowed.
    directlyOn(
        realContextImpl,
        ShadowContextImpl.CLASS_NAME,
        "startActivity",
        ClassParameter.from(Intent.class, intent),
        ClassParameter.from(Bundle.class, options)
    );
  }

  @Implementation(minSdk = JELLY_BEAN_MR1)
  protected int getUserId() {
    return 0;
  }

  @Implementation
  protected File getExternalCacheDir() {
    return Environment.getExternalStorageDirectory();
  }

  @Implementation(maxSdk = JELLY_BEAN_MR2)
  protected File getExternalFilesDir(String type) {
    return Environment.getExternalStoragePublicDirectory(type);
  }

  @Implementation(minSdk = KITKAT)
  protected File[] getExternalFilesDirs(String type) {
    return new File[] {Environment.getExternalStoragePublicDirectory(type)};
  }

  @Resetter
  public static void reset() {
    String prefsCacheFieldName =
        RuntimeEnvironment.getApiLevel() >= N ? "sSharedPrefsCache" : "sSharedPrefs";
    Object prefsDefaultValue = RuntimeEnvironment.getApiLevel() >= KITKAT ? null : new HashMap<>();
    Class<?> contextImplClass =
        ReflectionHelpers.loadClass(
            ShadowContextImpl.class.getClassLoader(), "android.app.ContextImpl");
    ReflectionHelpers.setStaticField(contextImplClass, prefsCacheFieldName, prefsDefaultValue);

    if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.LOLLIPOP_MR1) {
      HashMap<String, Object> fetchers =
          ReflectionHelpers.getStaticField(contextImplClass, "SYSTEM_SERVICE_MAP");
      Class staticServiceFetcherClass =
          ReflectionHelpers.loadClass(
              ShadowContextImpl.class.getClassLoader(),
              "android.app.ContextImpl$StaticServiceFetcher");

      for (Object o : fetchers.values()) {
        if (staticServiceFetcherClass.isInstance(o)) {
          ReflectionHelpers.setField(staticServiceFetcherClass, o, "mCachedInstance", null);
        }
      }

      if (RuntimeEnvironment.getApiLevel() >= KITKAT) {

        Object windowServiceFetcher = fetchers.get(Context.WINDOW_SERVICE);
        ReflectionHelpers.setField(
            windowServiceFetcher.getClass(), windowServiceFetcher, "mDefaultDisplay", null);
      }
    }
  }

  private ShadowInstrumentation getShadowInstrumentation() {
    ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
    return Shadow.extract(activityThread.getInstrumentation());
  }

  @Implementation
  public File getDatabasePath(String name) {
    // Windows is an abomination.
    if (File.separatorChar == '\\' && Paths.get(name).isAbsolute()) {
      String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
      File dir = new File(dirPath);
      name = name.substring(name.lastIndexOf(File.separatorChar));
      File f = new File(dir, name);
      if (!dir.isDirectory() && dir.mkdir()) {
        FileUtils.setPermissions(dir.getPath(), 505, -1, -1);
      }
      return f;
    } else {
      return directlyOn(realContextImpl, ShadowContextImpl.CLASS_NAME, "getDatabasePath",
          ClassParameter.from(String.class, name));
    }
  }

  /** Accessor interface for {@link android.app.ContextImpl}'s internals. */
  @ForType(className = CLASS_NAME)
  public interface _ContextImpl_ {
    @Static
    Context createSystemContext(ActivityThread activityThread);

    @Static
    Context createAppContext(ActivityThread activityThread, LoadedApk loadedApk);

    void setOuterContext(Context context);
  }
}


package org.robolectric.shadows;

import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Build;

/** Utilities for {@link ResolveInfo}. */
// TODO: Create a ResolveInfoBuilder in androidx and migrate factory methods there.
public class ShadowResolveInfo {

  /**
   * Creates a {@link ResolveInfo}.
   *
   * @param displayName Display name.
   * @param packageName Package name.
   * @return Resolve info instance.
   */
  public static ResolveInfo newResolveInfo(String displayName, String packageName) {
    return newResolveInfo(displayName, packageName, null);
  }

  /**
   * Creates a {@link ResolveInfo}.
   *
   * @param displayName Display name.
   * @param packageName Package name.
   * @param activityName Activity name.
   * @return Resolve info instance.
   */
  public static ResolveInfo newResolveInfo(String displayName, String packageName, String activityName) {
    ResolveInfo resInfo = new ResolveInfo();
    ActivityInfo actInfo = new ActivityInfo();
    actInfo.applicationInfo = new ApplicationInfo();
    actInfo.packageName = packageName;
    actInfo.applicationInfo.packageName = packageName;
    actInfo.name = activityName;
    resInfo.activityInfo = actInfo;
    resInfo.nonLocalizedLabel = displayName;
    return resInfo;
  }

  /**
   * Copies {@link ResolveInfo}.
   *
   * <p>Note that this is shallow copy as performed by the copy constructor existing in API 17.
   */
  public static ResolveInfo newResolveInfo(ResolveInfo orig) {
    ResolveInfo copy;
    if (Build.VERSION.SDK_INT >= 17) {
      copy = new ResolveInfo(orig);
    } else {
      copy = new ResolveInfo();
      copy.activityInfo = orig.activityInfo;
      copy.serviceInfo = orig.serviceInfo;
      copy.filter = orig.filter;
      copy.priority = orig.priority;
      copy.preferredOrder = orig.preferredOrder;
      copy.match = orig.match;
      copy.specificIndex = orig.specificIndex;
      copy.labelRes = orig.labelRes;
      copy.nonLocalizedLabel = orig.nonLocalizedLabel;
      copy.icon = orig.icon;
      copy.resolvePackageName = orig.resolvePackageName;
    }
    // For some reason isDefault field is not copied by the copy constructor.
    copy.isDefault = orig.isDefault;
    return copy;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;

import android.os.SystemClock;
import java.time.DateTimeException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

/**
 * A shadow SystemClock used when {@link LooperMode.Mode.PAUSED} is active.
 *
 *
In this variant, there is just one global system time controlled by this class. The current
 * time is fixed in place, and manually advanced by calling {@link
 * SystemClock#setCurrentTimeMillis(long)}
 *
 *
{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are
 * identical.
 *
 *
This class should not be referenced directly. Use ShadowSystemClock instead.
 */
@Implements(
    value = SystemClock.class,
    isInAndroidSdk = false,
    shadowPicker = ShadowSystemClock.Picker.class)
public class ShadowPausedSystemClock extends ShadowSystemClock {
  private static final long INITIAL_TIME = 100;
  private static final int MILLIS_PER_NANO = 1000000;;
  private static long currentTimeMillis = INITIAL_TIME;
  private static List<Listener> listeners = new CopyOnWriteArrayList<>();

  /**
   * Callback for clock updates
   */
  interface Listener {
    void clockUpdated(long newCurrentTimeMillis);
  }

  static void addListener(Listener listener) {
    listeners.add(listener);
  }

  static void removeListener(Listener listener) {
    listeners.remove(listener);
  }

  /** Advances the current time by given millis, without sleeping the current thread/ */
  @Implementation
  protected static void sleep(long millis) {
    currentTimeMillis += millis;
  }

  /**
   * Sets the current wall time.
   *
   *
Currently does not perform any permission checks.
   *
   * @return false if specified time is less than current time.
   */
  @Implementation
  protected static boolean setCurrentTimeMillis(long millis) {
    if (currentTimeMillis > millis) {
      return false;
    }

    currentTimeMillis = millis;
    for (Listener listener : listeners) {
      listener.clockUpdated(currentTimeMillis);
    }
    return true;
  }

  @Implementation
  protected static long uptimeMillis() {
    return currentTimeMillis;
  }

  @Implementation
  protected static long elapsedRealtime() {
    return uptimeMillis();
  }

  @Implementation(minSdk = JELLY_BEAN_MR1)
  protected static long elapsedRealtimeNanos() {
    return elapsedRealtime() * MILLIS_PER_NANO;
  }

  @Implementation
  protected static long currentThreadTimeMillis() {
    return uptimeMillis();
  }

  @HiddenApi
  @Implementation
  protected static long currentThreadTimeMicro() {
    return uptimeMillis() * 1000;
  }

  @HiddenApi
  @Implementation
  protected static long currentTimeMicro() {
    return currentThreadTimeMicro();
  }

  @Implementation(minSdk = P)
  @HiddenApi
  protected static long currentNetworkTimeMillis() {
    if (networkTimeAvailable) {
      return currentTimeMillis;
    } else {
      throw new DateTimeException("Network time not available");
    }
  }

  @Resetter
  public static void reset() {
    currentTimeMillis = INITIAL_TIME;
    ShadowSystemClock.reset();
  }
}


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.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.os.Build;
import android.os.Message;
import android.os.MessageQueue;
import android.os.MessageQueue.IdleHandler;
import android.os.SystemClock;
import java.time.Duration;
import java.util.ArrayList;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.res.android.NativeObjRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.Scheduler;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;

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

  @RealObject private MessageQueue realQueue;

  // just use this class as the native object
  private static NativeObjRegistry<ShadowPausedMessageQueue> nativeQueueRegistry =
      new NativeObjRegistry<ShadowPausedMessageQueue>(ShadowPausedMessageQueue.class);
  private boolean isPolling = false;
  private ShadowPausedSystemClock.Listener clockListener;

  // shadow constructor instead of nativeInit because nativeInit signature has changed across SDK
  // versions
  @Implementation
  protected void __constructor__(boolean quitAllowed) {
    invokeConstructor(MessageQueue.class, realQueue, from(boolean.class, quitAllowed));
    int ptr = (int) nativeQueueRegistry.register(this);
    reflector(ReflectorMessageQueue.class, realQueue).setPtr(ptr);
    clockListener =
        newCurrentTimeMillis -> nativeWake(ptr);
    ShadowPausedSystemClock.addListener(clockListener);
  }

  @Implementation(maxSdk = JELLY_BEAN_MR1)
  protected void nativeDestroy() {
    nativeDestroy(reflector(ReflectorMessageQueue.class, realQueue).getPtr());
  }

  @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT)
  protected static void nativeDestroy(int ptr) {
    nativeDestroy((long) ptr);
  }

  @Implementation(minSdk = KITKAT_WATCH)
  protected static void nativeDestroy(long ptr) {
    ShadowPausedMessageQueue q = nativeQueueRegistry.unregister(ptr);
    ShadowPausedSystemClock.removeListener(q.clockListener);
  }

  @Implementation(maxSdk = JELLY_BEAN_MR1)
  protected void nativePollOnce(int ptr, int timeoutMillis) {
    nativePollOnce((long) ptr, timeoutMillis);
  }

  // use the generic Object parameter types here, to avoid conflicts with the non-static
  // nativePollOnce
  @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = LOLLIPOP_MR1)
  protected static void nativePollOnce(Object ptr, Object timeoutMillis) {
    long ptrLong = getLong(ptr);
    nativeQueueRegistry.getNativeObject(ptrLong).nativePollOnce(ptrLong, (int) timeoutMillis);
  }

  @Implementation(minSdk = M)
  protected void nativePollOnce(long ptr, int timeoutMillis) {
    if (timeoutMillis == 0) {
      return;
    }
    synchronized (realQueue) {
      // only block if queue is empty
      // ignore timeout since clock is not advancing. ClockListener will notify when clock advances
      while (isIdle() && !isQuitting()) {
        isPolling = true;
        try {
          realQueue.wait();
        } catch (InterruptedException e) {
          // ignore
        }
      }
      isPolling = false;
    }
  }

  @Implementation(maxSdk = JELLY_BEAN_MR1)
  protected void nativeWake(int ptr) {
    synchronized (realQueue) {
      realQueue.notifyAll();
    }
  }

  // use the generic Object parameter types here, to avoid conflicts with the non-static
  // nativeWake
  @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT)
  protected static void nativeWake(Object ptr) {
    // JELLY_BEAN_MR2 has a bug where nativeWake can get called when pointer has already been
    // destroyed. See here where nativeWake is called outside the synchronized block
    // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr2-release/core/java/android/os/MessageQueue.java#239
    // So check to see if native object exists first
    ShadowPausedMessageQueue q = nativeQueueRegistry.peekNativeObject(getLong(ptr));
    if (q != null) {
      q.nativeWake(getInt(ptr));
    }
  }

  @Implementation(minSdk = KITKAT_WATCH)
  protected static void nativeWake(long ptr) {
    nativeQueueRegistry.getNativeObject(ptr).nativeWake((int) ptr);
  }

  @Implementation(minSdk = M)
  protected static boolean nativeIsPolling(long ptr) {
    return nativeQueueRegistry.getNativeObject(ptr).isPolling;
  }

  /** Exposes the API23+_isIdle method to older platforms */
  @Implementation(minSdk = 23)
  public boolean isIdle() {
    if (Build.VERSION.SDK_INT >= M) {
      return directlyOn(realQueue, MessageQueue.class).isIdle();
    } else {
      ReflectorMessageQueue internalQueue = reflector(ReflectorMessageQueue.class, realQueue);
      // this is a copy of the implementation from P
      synchronized (realQueue) {
        final long now = SystemClock.uptimeMillis();
        Message headMsg = internalQueue.getMessages();
        if (headMsg == null) {
          return true;
        }
        long when = shadowOfMsg(headMsg).getWhen();
        return now < when;
      }
    }
  }

  Message getNext() {
    return reflector(ReflectorMessageQueue.class, realQueue).next();
  }

  boolean isQuitAllowed() {
    return reflector(ReflectorMessageQueue.class, realQueue).getQuitAllowed();
  }

  void doEnqueueMessage(Message msg, long when) {
    reflector(ReflectorMessageQueue.class, realQueue).enqueueMessage(msg, when);
  }

  Message getMessages() {
    return reflector(ReflectorMessageQueue.class, realQueue).getMessages();
  }

  boolean isPolling() {
    synchronized (realQueue) {
      return isPolling;
    }
  }

  void quit() {
    if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
      reflector(ReflectorMessageQueue.class, realQueue).quit(false);
    } else {
      reflector(ReflectorMessageQueue.class, realQueue).quit();
    }
  }

  private boolean isQuitting() {
    if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
      return reflector(ReflectorMessageQueue.class, realQueue).getQuitting();
    } else {
      return reflector(ReflectorMessageQueue.class, realQueue).getQuiting();
    }
  }

  private static long getLong(Object intOrLongObj) {
    if (intOrLongObj instanceof Long) {
      return (long) intOrLongObj;
    } else {
      Integer intObj = (Integer) intOrLongObj;
      return intObj.longValue();
    }
  }

  private static int getInt(Object intOrLongObj) {
    if (intOrLongObj instanceof Integer) {
      return (int) intOrLongObj;
    } else {
      Long longObj = (Long) intOrLongObj;
      return longObj.intValue();
    }
  }

  Duration getNextScheduledTaskTime() {
    Message head = getMessages();
    if (head == null) {
      return Duration.ZERO;
    }
    return Duration.ofMillis(shadowOfMsg(head).getWhen());
  }

  Duration getLastScheduledTaskTime() {
    long when = 0;
    synchronized (realQueue) {
      Message next = getMessages();
      while (next != null) {
        when = shadowOfMsg(next).getWhen();
        next = shadowOfMsg(next).internalGetNext();
      }
    }
    return Duration.ofMillis(when);
  }

  Message poll() {
    synchronized (realQueue) {
      Message head = getMessages();
      if (head != null) {
        Message next = shadowOfMsg(head).internalGetNext();
        reflector(ReflectorMessageQueue.class, realQueue).setMessages(next);
      }
      return head;
    }
  }

  // TODO: reconsider exposing this as a public API. Only ShadowPausedLooper needs to access this,
  // so it should be package private
  @Override
  public void reset() {
    ReflectorMessageQueue msgQueue = reflector(ReflectorMessageQueue.class, realQueue);
    msgQueue.setMessages(null);
    msgQueue.setIdleHandlers(new ArrayList<>());
    msgQueue.setNextBarrierToken(0);
  }

  private static ShadowPausedMessage shadowOfMsg(Message head) {
    return Shadow.extract(head);
  }

  @Override
  public Scheduler getScheduler() {
    throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
  }

  @Override
  public void setScheduler(Scheduler scheduler) {
    throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
  }

  // intentionally do not support direct access to MessageQueue internals

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

  @Override
  public void setHead(Message msg) {
    throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
  }

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

    void enqueueMessage(Message msg, long when);

    Message next();

    @Accessor("mMessages")
    void setMessages(Message msg);

    @Accessor("mMessages")
    Message getMessages();

    @Accessor("mIdleHandlers")
    void setIdleHandlers(ArrayList<IdleHandler> list);

    @Accessor("mNextBarrierToken")
    void setNextBarrierToken(int token);

    @Accessor("mQuitAllowed")
    boolean getQuitAllowed();

    @Accessor("mPtr")
    void setPtr(int ptr);

    @Accessor("mPtr")
    int getPtr();

    // for APIs < JELLYBEAN_MR2
    void quit();

    void quit(boolean b);

    // for APIs < KITKAT
    @Accessor("mQuiting")
    boolean getQuiting();

    @Accessor("mQuitting")
    boolean getQuitting();
  }
}


package org.robolectric;

import static org.robolectric.shadows.ShadowAssetManager.useLegacy;

import android.annotation.IdRes;
import android.app.Activity;
import android.app.Fragment;
import android.app.IntentService;
import android.app.Service;
import android.app.backup.BackupAgent;
import android.content.ContentProvider;
import android.content.Intent;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import org.robolectric.android.AttributeSetBuilderImpl;
import org.robolectric.android.AttributeSetBuilderImpl.ArscResourceResolver;
import org.robolectric.android.AttributeSetBuilderImpl.LegacyResourceResolver;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.android.controller.BackupAgentController;
import org.robolectric.android.controller.ContentProviderController;
import org.robolectric.android.controller.FragmentController;
import org.robolectric.android.controller.IntentServiceController;
import org.robolectric.android.controller.ServiceController;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Scheduler;

public class Robolectric {

  public static <T extends Service> ServiceController<T> buildService(Class<T> serviceClass) {
    return buildService(serviceClass, null);
  }

  public static <T extends Service> ServiceController<T> buildService(Class<T> serviceClass, Intent intent) {
    return ServiceController.of(ReflectionHelpers.callConstructor(serviceClass), intent);
  }

  public static <T extends Service> T setupService(Class<T> serviceClass) {
    return buildService(serviceClass).create().get();
  }

  public static <T extends IntentService> IntentServiceController<T> buildIntentService(Class<T> serviceClass) {
    return buildIntentService(serviceClass, null);
  }

  public static <T extends IntentService> IntentServiceController<T> buildIntentService(Class<T> serviceClass, Intent intent) {
    return IntentServiceController.of(ReflectionHelpers.callConstructor(serviceClass), intent);
  }

  public static <T extends IntentService> T setupIntentService(Class<T> serviceClass) {
    return buildIntentService(serviceClass).create().get();
  }

  public static <T extends ContentProvider> ContentProviderController<T> buildContentProvider(Class<T> contentProviderClass) {
    return ContentProviderController.of(ReflectionHelpers.callConstructor(contentProviderClass));
  }

  public static <T extends ContentProvider> T setupContentProvider(Class<T> contentProviderClass) {
    return buildContentProvider(contentProviderClass).create().get();
  }

  public static <T extends ContentProvider> T setupContentProvider(Class<T> contentProviderClass, String authority) {
    return buildContentProvider(contentProviderClass).create(authority).get();
  }

  /**
   * Creates a ActivityController for the given activity class.
   *
   *
Consider using {@link androidx.test.core.app.ActivityScenario} instead, which provides
   * higher-level, streamlined APIs to control the lifecycle and it works with instrumentation tests
   * too.
   */
  public static <T extends Activity> ActivityController<T> buildActivity(Class<T> activityClass) {
    return buildActivity(activityClass, null);
  }

  /**
   * Creates a ActivityController for the given activity class with the intent.
   *
   *
Note: the activity class is not determined by the intent.
   *
   *
Consider using {@link androidx.test.core.app.ActivityScenario} instead, which provides
   * higher-level, streamlined APIs to control the lifecycle and it works with instrumentation tests
   * too.
   */
  public static <T extends Activity> ActivityController<T> buildActivity(
      Class<T> activityClass, Intent intent) {
    return ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent);
  }

  /**
   * Simulates starting activity with the given class type and returns its reference.
   *
   *
Use {@link androidx.test.core.app.ActivityScenario} instead, which works with
   * instrumentation tests too.
   *
   * @deprecated use {@link androidx.test.core.app.ActivityScenario}
   */
  @Deprecated
  public static <T extends Activity> T setupActivity(Class<T> activityClass) {
    return buildActivity(activityClass).setup().get();
  }

  /**
   * Creates a FragmentController for the given fragment class.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(Class<T> fragmentClass) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass));
  }

  /**
   * Creates a FragmentController for the given fragment class with the arguments.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass, Bundle arguments) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), arguments);
  }

  /**
   * Creates a FragmentController for the given fragment class in the specified host activity.
   *
   *
In general, it's a bad practice to design a fragment having dependency to a specific
   * activity. Consider removing the dependency and use other {@link #buildFragment} method or
   * {@link androidx.fragment.app.testing.FragmentScenario}.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass, Class<? extends Activity> activityClass) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), activityClass);
  }

  /**
   * Creates a FragmentController for the given fragment class. The given intent is set to the host
   * activity.
   *
   *
Note: the host activity class is not determined by the intent.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass, Intent intent) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), intent);
  }

  /**
   * Creates a FragmentController for the given fragment class with the arguments. The given intent
   * is set to the host activity.
   *
   *
Note: the host activity class is not determined by the intent.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass, Intent intent, Bundle arguments) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), intent, arguments);
  }

  /**
   * Creates a FragmentController for the given fragment class in the specified host activity. The
   * given intent is set to the host activity.
   *
   *
Note: the host activity class is not determined by the intent.
   *
   *
In general, it's a bad practice to design a fragment having dependency to a specific
   * activity. Consider removing the dependency and use other {@link #buildFragment} method or
   * {@link androidx.fragment.app.testing.FragmentScenario}.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass, Class<? extends Activity> activityClass, Intent intent) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), activityClass, intent);
  }

  /**
   * Creates a FragmentController for the given fragment class in the specified host activity with
   * the arguments.
   *
   *
In general, it's a bad practice to design a fragment having dependency to a specific
   * activity. Consider removing the dependency and use other {@link #buildFragment} method or
   * {@link androidx.fragment.app.testing.FragmentScenario}.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass, Class<? extends Activity> activityClass, Bundle arguments) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), activityClass, arguments);
  }

  /**
   * Creates a FragmentController for the given fragment class in the specified host activity with
   * the arguments. The given intent is set to the host activity.
   *
   *
Note: the host activity class is not determined by the intent.
   *
   *
In general, it's a bad practice to design a fragment having dependency to a specific
   * activity. Consider removing the dependency and use other {@link #buildFragment} method or
   * {@link androidx.fragment.app.testing.FragmentScenario}.
   *
   *
FragmentController provides low-level APIs to control its lifecycle. Please consider using
   * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level
   * APIs and works with instrumentation tests too.
   *
   * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers
   *     to use androidx fragments, to test these use FragmentScenario.
   */
  @Deprecated
  public static <T extends Fragment> FragmentController<T> buildFragment(
      Class<T> fragmentClass,
      Class<? extends Activity> activityClass,
      Intent intent,
      Bundle arguments) {
    return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), activityClass, intent, arguments);
  }

  public static <T extends BackupAgent> BackupAgentController<T> buildBackupAgent(Class<T> backupAgentClass) {
    return BackupAgentController.of(ReflectionHelpers.callConstructor(backupAgentClass));
  }

  public static <T extends BackupAgent> T setupBackupAgent(Class<T> backupAgentClass) {
    return buildBackupAgent(backupAgentClass).create().get();
  }

  /**
   * Allows for the programmatic creation of an {@link AttributeSet}.
   *
   * Useful for testing {@link View} classes without the need for creating XML snippets.
   */
  public static org.robolectric.android.AttributeSetBuilder buildAttributeSet() {
    if (useLegacy()) {
      return new AttributeSetBuilderImpl(
          new LegacyResourceResolver(RuntimeEnvironment.application,
              RuntimeEnvironment.getCompileTimeResourceTable())) {};
    } else {
      return new AttributeSetBuilderImpl(
          new ArscResourceResolver(RuntimeEnvironment.application)) {};
    }
  }

  /**
   * Builder of {@link AttributeSet}s.
   *
   * @deprecated Use {@link org.robolectric.android.AttributeSetBuilder} instead.
   */
  @Deprecated
  public interface AttributeSetBuilder {
    /**
     * Set an attribute to the given value.
     *
     * The value will be interpreted according to the attribute's format.
     *
     * @param resId The attribute resource id to set.
     * @param value The value to set.
     * @return This {@link org.robolectric.android.AttributeSetBuilder}.
     */
    AttributeSetBuilder addAttribute(@IdRes int resId, String value);

    /**
     * Set the style attribute to the given value.
     *
     * The value will be interpreted as a resource reference.
     *
     * @param value The value for the specified attribute in this {@link AttributeSet}.
     * @return This {@link org.robolectric.android.AttributeSetBuilder}.
     */
    AttributeSetBuilder setStyleAttribute(String value);

    /**
     * Build an {@link AttributeSet} with the antecedent attributes.
     *
     * @return A new {@link AttributeSet}.
     */
    AttributeSet build();
  }

  /**
   * Return the foreground scheduler (e.g. the UI thread scheduler).
   *
   * @return Foreground scheduler.
   */
  public static Scheduler getForegroundThreadScheduler() {
    return ShadowApplication.getInstance().getForegroundThreadScheduler();
  }

  /**
   * Execute all runnables that have been enqueued on the foreground scheduler.
   */
  public static void flushForegroundThreadScheduler() {
    getForegroundThreadScheduler().advanceToLastPostedRunnable();
  }

  /**
   * Return the background scheduler.
   *
   * @return Background scheduler.
   */
  public static Scheduler getBackgroundThreadScheduler() {
    return ShadowApplication.getInstance().getBackgroundThreadScheduler();
  }

  /**
   * Execute all runnables that have been enqueued on the background scheduler.
   */
  public static void flushBackgroundThreadScheduler() {
    getBackgroundThreadScheduler().advanceToLastPostedRunnable();
  }
}


package org.robolectric.shadows;

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

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.Engine;
import android.speech.tts.UtteranceProgressListener;
import java.util.HashMap;
import org.robolectric.RuntimeEnvironment;
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;

@Implements(TextToSpeech.class)
public class ShadowTextToSpeech {

  @RealObject private TextToSpeech tts;

  private Context context;
  private TextToSpeech.OnInitListener listener;
  private String lastSpokenText;
  private boolean shutdown = false;
  private boolean stopped = true;
  private int queueMode = -1;

  @Implementation
  protected void __constructor__(Context context, TextToSpeech.OnInitListener listener) {
    this.context = context;
    this.listener = listener;
  }

  /**
   * Speaks the string using the specified queuing strategy and speech parameters.
   *
   * @param params The real implementation converts the hashmap into a bundle, but the bundle
   *     argument is not used in the shadow implementation.
   */
  @Implementation
  protected int speak(
      final String text, final int queueMode, final HashMap<String, String> params) {
    if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
      return Shadow.directlyOn(tts, TextToSpeech.class).speak(text, queueMode, params);
    }
    return speak(
        text, queueMode, null, params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
  }

  @Implementation(minSdk = LOLLIPOP)
  protected int speak(
      final CharSequence text, final int queueMode, final Bundle params, final String utteranceId) {
    stopped = false;
    lastSpokenText = text.toString();
    this.queueMode = queueMode;

    if (RuntimeEnvironment.getApiLevel() >= ICE_CREAM_SANDWICH_MR1) {
      if (utteranceId != null) {
        // The onStart and onDone callbacks are normally delivered asynchronously. Since in
        // Robolectric we don't need the wait for TTS package, the asynchronous callbacks are
        // simulated by posting it on a handler. The behavior of the callback can be changed for
        // each individual test by changing the idling mode of the foreground scheduler.
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(
            () -> {
              UtteranceProgressListener utteranceProgressListener = getUtteranceProgressListener();
              if (utteranceProgressListener != null) {
                utteranceProgressListener.onStart(utteranceId);
              }
              // The onDone callback is posted in a separate run-loop from onStart, so that tests
              // can pause the scheduler and test the behavior between these two callbacks.
              handler.post(
                  () -> {
                    UtteranceProgressListener utteranceProgressListener2 =
                        getUtteranceProgressListener();
                    if (utteranceProgressListener2 != null) {
                      utteranceProgressListener2.onDone(utteranceId);
                    }
                  });
            });
      }
    }
    return TextToSpeech.SUCCESS;
  }

  @Implementation
  protected void shutdown() {
    shutdown = true;
  }

  @Implementation
  protected int stop() {
    stopped = true;
    return TextToSpeech.SUCCESS;
  }

  private UtteranceProgressListener getUtteranceProgressListener() {
    return ReflectionHelpers.getField(tts, "mUtteranceProgressListener");
  }

  public Context getContext() {
    return context;
  }

  public TextToSpeech.OnInitListener getOnInitListener() {
    return listener;
  }

  public String getLastSpokenText() {
    return lastSpokenText;
  }

  public void clearLastSpokenText() {
    lastSpokenText = null;
  }

  public boolean isShutdown() {
    return shutdown;
  }

  /** @return {@code true} if the TTS is stopped. */
  public boolean isStopped() {
    return stopped;
  }

  public int getQueueMode() {
    return queueMode;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;

import android.graphics.Point;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.WifiDisplayStatus;
import android.os.RemoteException;
import android.view.Display;
import android.view.DisplayInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

@Implements(value = DisplayManagerGlobal.class, isInAndroidSdk = false, minSdk = JELLY_BEAN_MR1)
public class ShadowDisplayManagerGlobal {
  private static DisplayManagerGlobal instance;

  private float saturationLevel = 1f;

  private MyDisplayManager mDm;

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

  @Implementation
  synchronized public static DisplayManagerGlobal getInstance() {
    if (instance == null) {
      MyDisplayManager myIDisplayManager = new MyDisplayManager();
      IDisplayManager proxy = ReflectionHelpers.createDelegatingProxy(IDisplayManager.class, myIDisplayManager);
      instance = ReflectionHelpers.callConstructor(DisplayManagerGlobal.class,
          ClassParameter.from(IDisplayManager.class, proxy));
      ShadowDisplayManagerGlobal shadow = Shadow.extract(instance);
      shadow.mDm = myIDisplayManager;
    }
    return instance;
  }

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

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

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

    return mDm.addDisplay(displayInfo);
  }

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

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

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

  private static class MyDisplayManager {
    private final TreeMap<Integer, DisplayInfo> displayInfos = new TreeMap<>();
    private int nextDisplayId = 0;
    private final List<IDisplayManagerCallback> callbacks = new ArrayList<>();

    // @Override
    public DisplayInfo getDisplayInfo(int i) throws RemoteException {
      DisplayInfo displayInfo = displayInfos.get(i);
      return displayInfo == null ? null : new DisplayInfo(displayInfo);
    }

    // @Override // todo: use @Implements/@Implementation for signature checking
    public int[] getDisplayIds() throws RemoteException {
      int[] ids = new int[displayInfos.size()];
      int i = 0;
      for (Integer displayId : displayInfos.keySet()) {
        ids[i++] = displayId;
      }
      return ids;
    }

    // @Override
    public void registerCallback(IDisplayManagerCallback iDisplayManagerCallback) throws RemoteException {
      this.callbacks.add(iDisplayManagerCallback);
    }

    synchronized private int addDisplay(DisplayInfo displayInfo) {
      int nextId = nextDisplayId++;
      displayInfos.put(nextId, displayInfo);
      notifyListeners(nextId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
      return nextId;
    }

    synchronized private void changeDisplay(int displayId, DisplayInfo displayInfo) {
      if (!displayInfos.containsKey(displayId)) {
        throw new IllegalStateException("no display " + displayId);
      }

      displayInfos.put(displayId, displayInfo);
      notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
    }

    synchronized private void removeDisplay(int displayId) {
      if (!displayInfos.containsKey(displayId)) {
        throw new IllegalStateException("no display " + displayId);
      }

      displayInfos.remove(displayId);
      notifyListeners(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
    }

    private void notifyListeners(int nextId, int event) {
      for (IDisplayManagerCallback callback : callbacks) {
        try {
          callback.onDisplayEvent(nextId, event);
        } catch (RemoteException e) {
          throw new RuntimeException(e);
        }
      }
    }
  }

  @Implementation(minSdk = P, maxSdk = P)
  protected void setSaturationLevel(float level) {
    if (level < 0f || level > 1f) {
      throw new IllegalArgumentException("Saturation level must be between 0 and 1");
    }
    saturationLevel = level;
  }

  /**
   * Returns the current display saturation level; {@link android.os.Build.VERSION_CODES.P} only.
   */
  float getSaturationLevel() {
    return saturationLevel;
  }
}


package org.robolectric.res;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.WatchService;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.annotation.concurrent.GuardedBy;
import org.robolectric.util.Util;

@SuppressWarnings({"NewApi", "AndroidJdkLibsChecker"})
abstract public class Fs {

  @GuardedBy("ZIP_FILESYSTEMS")
  private static final Map<Path, FsWrapper> ZIP_FILESYSTEMS = new HashMap<>();

  /** @deprecated Use {@link File#toPath()} instead. */
  @Deprecated
  public static Path newFile(File file) {
    return file.toPath();
  }

  /** @deprecated Use {@link #fromUrl(String)} instead. */
  @Deprecated
  public static Path fileFromPath(String path) {
    return Fs.fromUrl(path);
  }

  public static FileSystem forJar(URL url) {
    return forJar(Paths.get(toUri(url)));
  }

  public static FileSystem forJar(Path jarFile) {
    try {
      return getJarFs(jarFile);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Use this method instead of {@link Paths#get(String, String...)} or {@link Paths#get(URI)}.
   *
   *
Supports "file:path", "jar:file:jarfile.jar!/path", and plain old paths.
   *
   *
For JAR files, automatically open and cache filesystems.
   */
  public static Path fromUrl(String urlString) {
    if (urlString.startsWith("file:") || urlString.startsWith("jar:")) {
      URL url;
      try {
        url = new URL(urlString);
      } catch (MalformedURLException e) {
        throw new RuntimeException("Failed to resolve path from " + urlString, e);
      }
      return fromUrl(url);
    } else {
      return Paths.get(urlString);
    }
  }

  /** Isn't this what {@link Paths#get(URI)} should do? */
  public static Path fromUrl(URL url) {
    try {
      switch (url.getProtocol()) {
        case "file":
          return Paths.get(url.toURI());
        case "jar":
          String[] parts = url.getPath().split("!", 0);
          Path jarFile = Paths.get(new URI(parts[0]).toURL().getFile());
          FileSystem fs = getJarFs(jarFile);
          return fs.getPath(parts[1].substring(1));
        default:
          throw new IllegalArgumentException("unsupported fs type for '" + url + "'");
      }
    } catch (Exception e) {
      throw new RuntimeException("Failed to resolve path from " + url, e);
    }
  }

  public static URI toUri(URL url) {
    try {
      return url.toURI();
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException("invalid URL: " + url, e);
    }
  }

  static String baseNameFor(Path path) {
    String name = path.getFileName().toString();
    int dotIndex = name.indexOf(".");
    return dotIndex >= 0 ? name.substring(0, dotIndex) : name;
  }

  public static InputStream getInputStream(Path path) throws IOException {
    // otherwise we get ClosedByInterruptException, meh
    if (path.toUri().getScheme().equals("file")) {
      return new BufferedInputStream(new FileInputStream(path.toFile()));
    }
    return new BufferedInputStream(Files.newInputStream(path));
  }

  public static byte[] getBytes(Path path) throws IOException {
    return Util.readBytes(getInputStream(path));
  }

  public static Path[] listFiles(Path path) throws IOException {
    try (Stream<Path> list = Files.list(path)) {
      return list.toArray(Path[]::new);
    }
  }

  public static Path[] listFiles(Path path, final Predicate<Path> filter) throws IOException {
    try (Stream<Path> list = Files.list(path)) {
      return list.filter(filter).toArray(Path[]::new);
    }
  }

  public static String[] listFileNames(Path path) {
    File[] files = path.toFile().listFiles();
    if (files == null) return null;
    String[] strings = new String[files.length];
    for (int i = 0; i < files.length; i++) {
      strings[i] = files[i].getName();
    }
    return strings;
  }

  public static Path join(Path path, String... pathParts) {
    for (String pathPart : pathParts) {
      path = path.resolve(pathPart);
    }
    return path;
  }

  public static String externalize(Path path) {
    if (path.getFileSystem().provider().getScheme().equals("file")) {
      return path.toString();
    } else {
      return path.toUri().toString();
    }
  }

  /** Returns a reference-counted Jar FileSystem, possibly one that was previously returned. */
  private static FileSystem getJarFs(Path jarFile) throws IOException {
    Path key = jarFile.toAbsolutePath();

    synchronized (ZIP_FILESYSTEMS) {
      FsWrapper fs = ZIP_FILESYSTEMS.get(key);
      if (fs == null) {
        fs = new FsWrapper(FileSystems.newFileSystem(key, null), key);
        fs.incrRefCount();

        ZIP_FILESYSTEMS.put(key, fs);
      } else {
        fs.incrRefCount();
      }

      return fs;
    }
  }

  @SuppressWarnings("NewApi")
  private static class FsWrapper extends FileSystem {
    private final FileSystem delegate;
    private final Path jarFile;

    @GuardedBy("this")
    private int refCount;

    public FsWrapper(FileSystem delegate, Path jarFile) {
      this.delegate = delegate;
      this.jarFile = jarFile;
    }

    synchronized void incrRefCount() {
      refCount++;
    }

    synchronized void decrRefCount() throws IOException {
      if (--refCount == 0) {
        synchronized (ZIP_FILESYSTEMS) {
          ZIP_FILESYSTEMS.remove(jarFile);
        }
        delegate.close();
      }
    }

    @Override
    public FileSystemProvider provider() {
      return delegate.provider();
    }

    @Override
    public void close() throws IOException {
      decrRefCount();
    }

    @Override
    public boolean isOpen() {
      return delegate.isOpen();
    }

    @Override
    public boolean isReadOnly() {
      return delegate.isReadOnly();
    }

    @Override
    public String getSeparator() {
      return delegate.getSeparator();
    }

    @Override
    public Iterable<Path> getRootDirectories() {
      return delegate.getRootDirectories();
    }

    @Override
    public Iterable<FileStore> getFileStores() {
      return delegate.getFileStores();
    }

    @Override
    public Set<String> supportedFileAttributeViews() {
      return delegate.supportedFileAttributeViews();
    }

    @Override
    public Path getPath(String first, String... more) {
      return delegate.getPath(first, more);
    }

    @Override
    public PathMatcher getPathMatcher(String syntaxAndPattern) {
      return delegate.getPathMatcher(syntaxAndPattern);
    }

    @Override
    public UserPrincipalLookupService getUserPrincipalLookupService() {
      return delegate.getUserPrincipalLookupService();
    }

    @Override
    public WatchService newWatchService() throws IOException {
      return delegate.newWatchService();
    }
  }
}


package org.robolectric.shadows;

import android.location.Address;
import android.location.Geocoder;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

@Implements(Geocoder.class)
public final class ShadowGeocoder {

  private static boolean isPresent = true;
  private List<Address> fromLocation = new ArrayList<>();

  /** @return `true` by default, or the value specified via {@link #setIsPresent(boolean)} */
  @Implementation
  protected static boolean isPresent() {
    return isPresent;
  }

  /**
   * Returns an empty list by default, or the last value set by {@link #setFromLocation(List)}
   *
   * `latitude` and `longitude` are ignored by this implementation, except to check that they are in
   * appropriate bounds. `maxResults` determines the
   * maximum number of addresses to return.
   */
  @Implementation
  protected List<Address> getFromLocation(double latitude, double longitude, int maxResults)
      throws IOException {
    Preconditions.checkArgument(
        -90 <= latitude && latitude <= 90, "Latitude must be between -90 and 90, got %s", latitude);
    Preconditions.checkArgument(
        -180 <= longitude && longitude <= 180,
        "Longitude must be between -180 and 180, got %s",
        longitude);
    return fromLocation.subList(0, Math.min(maxResults, fromLocation.size()));
  }

  /**
   * Sets the value to be returned by {@link Geocoder#isPresent()}.
   *
   * This value is reset to `true` for each test.
   */
  public static void setIsPresent(boolean value) {
    isPresent = value;
  }

  /** Sets the value to be returned by {@link Geocoder#getFromLocation(double, double, int)}. */
  public void setFromLocation(List<Address> list) {
    fromLocation = list;
  }

  @Resetter
  public static void reset() {
    isPresent = true;
  }
}


package org.robolectric.shadows;

import android.util.Log;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

@Implements(Log.class)
public class ShadowLog {
  private static final int extraLogLength = "l/: \n".length();
  private static final Map<String, Queue<LogItem>> logsByTag = Collections.synchronizedMap(new
      HashMap<String, Queue<LogItem>>());
  private static final Queue<LogItem> logs = new ConcurrentLinkedQueue<>();
  public static PrintStream stream;
  private static final Map<String, Integer> tagToLevel = Collections.synchronizedMap(new
      HashMap<String, Integer>());

  /**
   * Whether calling {@link Log#wtf} will throw {@link TerribleFailure}. This is analogous to
   * Android's {@link android.provider.Settings.Global#WTF_IS_FATAL}. The default value is false to
   * preserve existing behavior.
   */
  private static boolean wtfIsFatal = false;

  @Implementation
  protected static int e(String tag, String msg) {
    return e(tag, msg, null);
  }

  @Implementation
  protected static int e(String tag, String msg, Throwable throwable) {
    return addLog(Log.ERROR, tag, msg, throwable);
  }

  @Implementation
  protected static int d(String tag, String msg) {
    return d(tag, msg, null);
  }

  @Implementation
  protected static int d(String tag, String msg, Throwable throwable) {
    return addLog(Log.DEBUG, tag, msg, throwable);
  }

  @Implementation
  protected static int i(String tag, String msg) {
    return i(tag, msg, null);
  }

  @Implementation
  protected static int i(String tag, String msg, Throwable throwable) {
    return addLog(Log.INFO, tag, msg, throwable);
  }

  @Implementation
  protected static int v(String tag, String msg) {
    return v(tag, msg, null);
  }

  @Implementation
  protected static int v(String tag, String msg, Throwable throwable) {
    return addLog(Log.VERBOSE, tag, msg, throwable);
  }

  @Implementation
  protected static int w(String tag, String msg) {
    return w(tag, msg, null);
  }

  @Implementation
  protected static int w(String tag, Throwable throwable) {
    return w(tag, null, throwable);
  }

  @Implementation
  protected static int w(String tag, String msg, Throwable throwable) {
    return addLog(Log.WARN, tag, msg, throwable);
  }

  @Implementation
  protected static int wtf(String tag, String msg) {
    return wtf(tag, msg, null);
  }

  @Implementation
  protected static int wtf(String tag, String msg, Throwable throwable) {
    addLog(Log.ASSERT, tag, msg, throwable);
    if (wtfIsFatal) {
      throw new TerribleFailure(msg, throwable);
    }
    return 0;
  }

  /** Sets whether calling {@link Log#wtf} will throw {@link TerribleFailure}. */
  public static void setWtfIsFatal(boolean fatal) {
    wtfIsFatal = fatal;
  }

  @Implementation
  protected static boolean isLoggable(String tag, int level) {
    synchronized (tagToLevel) {
      if (tagToLevel.containsKey(tag)) {
        return level >= tagToLevel.get(tag);
      }
    }
    return stream != null || level >= Log.INFO;
  }

  @Implementation
  protected static int println_native(int bufID, int priority, String tag, String msg) {
    addLog(priority, tag, msg, null);
    int tagLength = tag == null ? 0 : tag.length();
    int msgLength = msg == null ? 0 : msg.length();
    return extraLogLength + tagLength + msgLength;
  }

  /**
   * Sets the log level of a given tag, that {@link #isLoggable} will follow.
   * @param tag A log tag
   * @param level A log level, from {@link android.util.Log}
   */
  public static void setLoggable(String tag, int level) {
    tagToLevel.put(tag, level);
  }

  private static int addLog(int level, String tag, String msg, Throwable throwable) {
    if (stream != null) {
      logToStream(stream, level, tag, msg, throwable);
    }

    LogItem item = new LogItem(level, tag, msg, throwable);
    Queue<LogItem> itemList;

    synchronized (logsByTag) {
      if (!logsByTag.containsKey(tag)) {
        itemList = new ConcurrentLinkedQueue<>();
        logsByTag.put(tag, itemList);
      } else {
        itemList = logsByTag.get(tag);
      }
    }

    itemList.add(item);
    logs.add(item);

    return 0;
  }

  private static void logToStream(PrintStream ps, int level, String tag, String msg, Throwable throwable) {
    final char c;
    switch (level) {
      case Log.ASSERT: c = 'A'; break;
      case Log.DEBUG:  c = 'D'; break;
      case Log.ERROR:  c = 'E'; break;
      case Log.WARN:   c = 'W'; break;
      case Log.INFO:   c = 'I'; break;
      case Log.VERBOSE:c = 'V'; break;
      default:         c = '?';
    }
    ps.println(c + "/" + tag + ": " + msg);
    if (throwable != null) {
      throwable.printStackTrace(ps);
    }
  }

  /**
   * Returns ordered list of all log entries.
   * @return List of log items
   */
  public static List<LogItem> getLogs() {
    return new ArrayList<>(logs);
  }

  /**
   * Returns ordered list of all log items for a specific tag.
   *
   * @param tag The tag to get logs for
   * @return The list of log items for the tag or an empty list if no logs for that tag exist.
   */
  public static List<LogItem> getLogsForTag(String tag) {
    Queue<LogItem> logs = logsByTag.get(tag);
    return logs == null ? Collections.emptyList() : new ArrayList<>(logs);
  }

  /** Clear all accumulated logs. */
  public static void clear() {
    reset();
  }

  @Resetter
  public static void reset() {
    logs.clear();
    logsByTag.clear();
    tagToLevel.clear();
    wtfIsFatal = false;
  }

  @SuppressWarnings("CatchAndPrintStackTrace")
  public static void setupLogging() {
    String logging = System.getProperty("robolectric.logging");
    if (logging != null && stream == null) {
      PrintStream stream = null;
      if ("stdout".equalsIgnoreCase(logging)) {
        stream = System.out;
      } else if ("stderr".equalsIgnoreCase(logging)) {
        stream = System.err;
      } else {
        try {
          final PrintStream file = new PrintStream(new FileOutputStream(logging), true);
          stream = file;
          Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override public void run() {
              try {
                file.close();
              } catch (Exception ignored) {
              }
            }
          });
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      ShadowLog.stream = stream;
    }
  }

  public static class LogItem {
    public final int type;
    public final String tag;
    public final String msg;
    public final Throwable throwable;

    public LogItem(int type, String tag, String msg, Throwable throwable) {
      this.type = type;
      this.tag = tag;
      this.msg = msg;
      this.throwable = throwable;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      LogItem log = (LogItem) o;
      return type == log.type
          && !(msg != null ? !msg.equals(log.msg) : log.msg != null)
          && !(tag != null ? !tag.equals(log.tag) : log.tag != null)
          && !(throwable != null ? !throwable.equals(log.throwable) : log.throwable != null);
    }

    @Override
    public int hashCode() {
      int result = type;
      result = 31 * result + (tag != null ? tag.hashCode() : 0);
      result = 31 * result + (msg != null ? msg.hashCode() : 0);
      result = 31 * result + (throwable != null ? throwable.hashCode() : 0);
      return result;
    }

    @Override
    public String toString() {
      return "LogItem{" +
          "type=" + type +
          ", tag='" + tag + '\'' +
          ", msg='" + msg + '\'' +
          ", throwable=" + throwable +
          '}';
    }
  }

  /**
   * Failure thrown when wtf_is_fatal is true and Log.wtf is called. This is a parallel
   * implementation of framework's hidden API {@link android.util.Log#TerribleFailure}, to allow
   * tests to catch / expect these exceptions.
   */
  public static class TerribleFailure extends RuntimeException {
    public TerribleFailure(String msg, Throwable cause) {
      super(msg, cause);
    }
  }
}