package org.robolectric.shadows;

import android.os.AsyncTask;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;

/** A {@link AsyncTask} shadow for {@link LooperMode.Mode.LEGACY}. */
@Implements(
    value = AsyncTask.class,
    shadowPicker = ShadowAsyncTask.Picker.class,
    // TODO: turn off shadowOf generation. Figure out why this is needed
    isInAndroidSdk = false)
public class ShadowLegacyAsyncTask<Params, Progress, Result> extends ShadowAsyncTask {

  @RealObject private AsyncTask<Params, Progress, Result> realAsyncTask;

  private final FutureTask<Result> future;
  private final BackgroundWorker worker;
  private AsyncTask.Status status = AsyncTask.Status.PENDING;

  public ShadowLegacyAsyncTask() {
    worker = new BackgroundWorker();
    future =
        new FutureTask<Result>(worker) {
          @Override
          protected void done() {
            status = AsyncTask.Status.FINISHED;
            try {
              final Result result = get();

              try {
                ShadowApplication.getInstance()
                    .getForegroundThreadScheduler()
                    .post(
                        new Runnable() {
                          @Override
                          public void run() {
                            getBridge().onPostExecute(result);
                          }
                        });
              } catch (Throwable t) {
                throw new OnPostExecuteException(t);
              }
            } catch (CancellationException e) {
              ShadowApplication.getInstance()
                  .getForegroundThreadScheduler()
                  .post(
                      new Runnable() {
                        @Override
                        public void run() {
                          getBridge().onCancelled();
                        }
                      });
            } catch (InterruptedException e) {
              // Ignore.
            } catch (OnPostExecuteException e) {
              throw new RuntimeException(e.getCause());
            } catch (Throwable t) {
              throw new RuntimeException(
                  "An error occurred while executing doInBackground()", t.getCause());
            }
          }
        };
  }

  @Implementation
  protected boolean isCancelled() {
    return future.isCancelled();
  }

  @Implementation
  protected boolean cancel(boolean mayInterruptIfRunning) {
    return future.cancel(mayInterruptIfRunning);
  }

  @Implementation
  protected Result get() throws InterruptedException, ExecutionException {
    return future.get();
  }

  @Implementation
  protected Result get(long timeout, TimeUnit unit)
      throws InterruptedException, ExecutionException, TimeoutException {
    return future.get(timeout, unit);
  }

  @Implementation
  protected AsyncTask<Params, Progress, Result> execute(final Params... params) {
    status = AsyncTask.Status.RUNNING;
    getBridge().onPreExecute();

    worker.params = params;

    ShadowApplication.getInstance()
        .getBackgroundThreadScheduler()
        .post(
            new Runnable() {
              @Override
              public void run() {
                future.run();
              }
            });

    return realAsyncTask;
  }

  @Implementation
  protected AsyncTask<Params, Progress, Result> executeOnExecutor(
      Executor executor, Params... params) {
    status = AsyncTask.Status.RUNNING;
    getBridge().onPreExecute();

    worker.params = params;
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            future.run();
          }
        });

    return realAsyncTask;
  }

  @Implementation
  protected AsyncTask.Status getStatus() {
    return status;
  }

  /**
   * Enqueue a call to {@link AsyncTask#onProgressUpdate(Object[])} on UI looper (or run it
   * immediately if the looper it is not paused).
   *
   * @param values The progress values to update the UI with.
   * @see AsyncTask#publishProgress(Object[])
   */
  @Implementation
  protected void publishProgress(final Progress... values) {
    ShadowApplication.getInstance()
        .getForegroundThreadScheduler()
        .post(
            new Runnable() {
              @Override
              public void run() {
                getBridge().onProgressUpdate(values);
              }
            });
  }

  private ShadowAsyncTaskBridge<Params, Progress, Result> getBridge() {
    return new ShadowAsyncTaskBridge<>(realAsyncTask);
  }

  private final class BackgroundWorker implements Callable<Result> {
    Params[] params;

    @Override
    public Result call() throws Exception {
      return getBridge().doInBackground(params);
    }
  }

  private static class OnPostExecuteException extends Exception {
    public OnPostExecuteException(Throwable throwable) {
      super(throwable);
    }
  }
}


package org.robolectric.android;

import static android.content.res.Configuration.DENSITY_DPI_ANY;
import static android.content.res.Configuration.DENSITY_DPI_NONE;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;

import android.content.res.Configuration;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.LocaleList;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.robolectric.RuntimeEnvironment;

// adapted from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/java/android/content/res/Configuration.java
public class ConfigurationV25 {

  private static String localesToResourceQualifier(List<Locale> locs) {
    final StringBuilder sb = new StringBuilder();
    for (int i = 0; i < locs.size(); i++) {
      final Locale loc = locs.get(i);
      final int l = loc.getLanguage().length();
      if (l == 0) {
        continue;
      }
      final int s = loc.getScript().length();
      final int c = loc.getCountry().length();
      final int v = loc.getVariant().length();
      // We ignore locale extensions, since they are not supported by AAPT

      if (sb.length() != 0) {
        sb.append(",");
      }
      if (l == 2 && s == 0 && (c == 0 || c == 2) && v == 0) {
        // Traditional locale format: xx or xx-rYY
        sb.append(loc.getLanguage());
        if (c == 2) {
          sb.append("-r").append(loc.getCountry());
        }
      } else {
        sb.append("b+");
        sb.append(loc.getLanguage());
        if (s != 0) {
          sb.append("+");
          sb.append(loc.getScript());
        }
        if (c != 0) {
          sb.append("+");
          sb.append(loc.getCountry());
        }
        if (v != 0) {
          sb.append("+");
          sb.append(loc.getVariant());
        }
      }
    }
    return sb.toString();
  }

  /**
   * Returns a string representation of the configuration that can be parsed
   * by build tools (like AAPT).
   *
   * @hide
   */
  public static String resourceQualifierString(Configuration config, DisplayMetrics displayMetrics) {
    return resourceQualifierString(config, displayMetrics, true);
  }

  public static String resourceQualifierString(Configuration config, DisplayMetrics displayMetrics, boolean includeSdk) {
    ArrayList<String> parts = new ArrayList<String>();

    if (config.mcc != 0) {
      parts.add("mcc" + config.mcc);
      if (config.mnc != 0) {
        parts.add("mnc" + config.mnc);
      }
    }

    List<Locale> locales = getLocales(config);
    if (!locales.isEmpty()) {
      final String resourceQualifier = localesToResourceQualifier(locales);
      if (!resourceQualifier.isEmpty()) {
        parts.add(resourceQualifier);
      }
    }

    switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) {
      case Configuration.SCREENLAYOUT_LAYOUTDIR_LTR:
        parts.add("ldltr");
        break;
      case Configuration.SCREENLAYOUT_LAYOUTDIR_RTL:
        parts.add("ldrtl");
        break;
      default:
        break;
    }

    if (config.smallestScreenWidthDp != 0) {
      parts.add("sw" + config.smallestScreenWidthDp + "dp");
    }

    if (config.screenWidthDp != 0) {
      parts.add("w" + config.screenWidthDp + "dp");
    }

    if (config.screenHeightDp != 0) {
      parts.add("h" + config.screenHeightDp + "dp");
    }

    switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) {
      case Configuration.SCREENLAYOUT_SIZE_SMALL:
        parts.add("small");
        break;
      case Configuration.SCREENLAYOUT_SIZE_NORMAL:
        parts.add("normal");
        break;
      case Configuration.SCREENLAYOUT_SIZE_LARGE:
        parts.add("large");
        break;
      case Configuration.SCREENLAYOUT_SIZE_XLARGE:
        parts.add("xlarge");
        break;
      default:
        break;
    }

    switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) {
      case Configuration.SCREENLAYOUT_LONG_YES:
        parts.add("long");
        break;
      case Configuration.SCREENLAYOUT_LONG_NO:
        parts.add("notlong");
        break;
      default:
        break;
    }

    switch (config.screenLayout & Configuration.SCREENLAYOUT_ROUND_MASK) {
      case Configuration.SCREENLAYOUT_ROUND_YES:
        parts.add("round");
        break;
      case Configuration.SCREENLAYOUT_ROUND_NO:
        parts.add("notround");
        break;
      default:
        break;
    }

    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.O) {
      switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) {
        case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES:
          parts.add("widecg");
          break;
        case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO:
          parts.add("nowidecg");
          break;
        default:
          break;
      }

      switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) {
        case Configuration.COLOR_MODE_HDR_YES:
          parts.add("highdr");
          break;
        case Configuration.COLOR_MODE_HDR_NO:
          parts.add("lowdr");
          break;
        default:
          break;
      }
    }

    switch (config.orientation) {
      case Configuration.ORIENTATION_LANDSCAPE:
        parts.add("land");
        break;
      case Configuration.ORIENTATION_PORTRAIT:
        parts.add("port");
        break;
      default:
        break;
    }

    switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) {
      case Configuration.UI_MODE_TYPE_APPLIANCE:
        parts.add("appliance");
        break;
      case Configuration.UI_MODE_TYPE_DESK:
        parts.add("desk");
        break;
      case Configuration.UI_MODE_TYPE_TELEVISION:
        parts.add("television");
        break;
      case Configuration.UI_MODE_TYPE_CAR:
        parts.add("car");
        break;
      case Configuration.UI_MODE_TYPE_WATCH:
        parts.add("watch");
        break;
      case Configuration.UI_MODE_TYPE_VR_HEADSET:
        parts.add("vrheadset");
        break;
      case Configuration.UI_MODE_TYPE_NORMAL:
      default:
        break;
    }

    switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
      case Configuration.UI_MODE_NIGHT_YES:
        parts.add("night");
        break;
      case Configuration.UI_MODE_NIGHT_NO:
        parts.add("notnight");
        break;
      default:
        break;
    }

    int densityDpi;
    if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN) {
      densityDpi = config.densityDpi;
    } else {
      densityDpi = displayMetrics.densityDpi;
    }

    switch (densityDpi) {
      case DENSITY_DPI_UNDEFINED:
        break;
      case 120:
        parts.add("ldpi");
        break;
      case 160:
        parts.add("mdpi");
        break;
      case 213:
        parts.add("tvdpi");
        break;
      case 240:
        parts.add("hdpi");
        break;
      case 320:
        parts.add("xhdpi");
        break;
      case 480:
        parts.add("xxhdpi");
        break;
      case 640:
        parts.add("xxxhdpi");
        break;
      case DENSITY_DPI_ANY:
        parts.add("anydpi");
        break;
      case DENSITY_DPI_NONE:
        parts.add("nodpi");
        break;
      default:
        parts.add(densityDpi + "dpi");
        break;
    }

    switch (config.touchscreen) {
      case Configuration.TOUCHSCREEN_NOTOUCH:
        parts.add("notouch");
        break;
      case Configuration.TOUCHSCREEN_FINGER:
        parts.add("finger");
        break;
      default:
        break;
    }

    switch (config.keyboardHidden) {
      case Configuration.KEYBOARDHIDDEN_NO:
        parts.add("keysexposed");
        break;
      case Configuration.KEYBOARDHIDDEN_YES:
        parts.add("keyshidden");
        break;
      case Configuration.KEYBOARDHIDDEN_SOFT:
        parts.add("keyssoft");
        break;
      default:
        break;
    }

    switch (config.keyboard) {
      case Configuration.KEYBOARD_NOKEYS:
        parts.add("nokeys");
        break;
      case Configuration.KEYBOARD_QWERTY:
        parts.add("qwerty");
        break;
      case Configuration.KEYBOARD_12KEY:
        parts.add("12key");
        break;
      default:
        break;
    }

    switch (config.navigationHidden) {
      case Configuration.NAVIGATIONHIDDEN_NO:
        parts.add("navexposed");
        break;
      case Configuration.NAVIGATIONHIDDEN_YES:
        parts.add("navhidden");
        break;
      default:
        break;
    }

    switch (config.navigation) {
      case Configuration.NAVIGATION_NONAV:
        parts.add("nonav");
        break;
      case Configuration.NAVIGATION_DPAD:
        parts.add("dpad");
        break;
      case Configuration.NAVIGATION_TRACKBALL:
        parts.add("trackball");
        break;
      case Configuration.NAVIGATION_WHEEL:
        parts.add("wheel");
        break;
      default:
        break;
    }

    if (includeSdk) {
      parts.add("v" + Build.VERSION.RESOURCES_SDK_INT);
    }

    return TextUtils.join("-", parts);
  }

  private static List<Locale> getLocales(Configuration config) {
    List<Locale> locales = new ArrayList<>();
    if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.M) {
      LocaleList localeList = config.getLocales();
      for (int i = 0; i < localeList.size(); i++) {
        locales.add(localeList.get(i));
      }
    } else if (config.locale != null) {
      locales.add(config.locale);
    }
    return locales;
  }
}


package org.robolectric.shadows;

import static android.bluetooth.BluetoothDevice.BOND_NONE;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static org.robolectric.shadow.api.Shadow.directlyOn;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.IBluetooth;
import android.content.Context;
import android.os.Handler;
import android.os.ParcelUuid;
import java.util.ArrayList;
import java.util.List;
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(BluetoothDevice.class)
public class ShadowBluetoothDevice {

  public static BluetoothDevice newInstance(String address) {
    return ReflectionHelpers.callConstructor(
        BluetoothDevice.class, ReflectionHelpers.ClassParameter.from(String.class, address));
  }

  @RealObject private BluetoothDevice realBluetoothDevice;
  private String name;
  private ParcelUuid[] uuids;
  private int bondState = BOND_NONE;
  private boolean createdBond = false;
  private boolean fetchUuidsWithSdpResult = false;
  private int fetchUuidsWithSdpCount = 0;
  private int type = BluetoothDevice.DEVICE_TYPE_UNKNOWN;
  private final List<BluetoothGatt> bluetoothGatts = new ArrayList<>();

  /**
   * Implements getService() in the same way the original method does, but ignores any Exceptions
   * from invoking {@link android.bluetooth.BluetoothAdapter#getBluetoothService}.
   */
  @Implementation
  protected static IBluetooth getService() {
    // Attempt to call the underlying getService method, but ignore any Exceptions. This allows us
    // to easily create BluetoothDevices for testing purposes without having any actual Bluetooth
    // capability.
    try {
      return directlyOn(BluetoothDevice.class, "getService");
    } catch (Exception e) {
      // No-op.
    }
    return null;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Implementation
  protected String getName() {
    return name;
  }

  /** Sets the return value for {@link BluetoothDevice#getType}. */
  public void setType(int type) {
    this.type = type;
  }

  /**
   * Overrides behavior of {@link BluetoothDevice#getType} to return pre-set result.
   *
   * @return Value set by calling {@link ShadowBluetoothDevice#setType}. If setType has not
   *     previously been called, will return BluetoothDevice.DEVICE_TYPE_UNKNOWN.
   */
  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected int getType() {
    return type;
  }

  /** Sets the return value for {@link BluetoothDevice#getUuids}. */
  public void setUuids(ParcelUuid[] uuids) {
    this.uuids = uuids;
  }

  /**
   * Overrides behavior of {@link BluetoothDevice#getUuids} to return pre-set result.
   *
   * @returns Value set by calling {@link ShadowBluetoothDevice#setUuids}. If setUuids has not
   *     previously been called, will return null.
   */
  @Implementation
  protected ParcelUuid[] getUuids() {
    return uuids;
  }

  /** Sets value of bond state for {@link BluetoothDevice#getBondState}. */
  public void setBondState(int bondState) {
    this.bondState = bondState;
  }

  /**
   * Overrides behavior of {@link BluetoothDevice#getBondState} to return pre-set result.
   *
   * @returns Value set by calling {@link ShadowBluetoothDevice#setBondState}. If setBondState has
   *     not previously been called, will return {@link BluetoothDevice#BOND_NONE} to indicate the
   *     device is not bonded.
   */
  @Implementation
  protected int getBondState() {
    return bondState;
  }

  /** Sets whether this device has been bonded with. */
  public void setCreatedBond(boolean createdBond) {
    this.createdBond = createdBond;
  }

  /** Returns whether this device has been bonded with. */
  @Implementation
  protected boolean createBond() {
    return createdBond;
  }

  /** Sets value of the return result for {@link BluetoothDevice#fetchUuidsWithSdp}. */
  public void setFetchUuidsWithSdpResult(boolean fetchUuidsWithSdpResult) {
    this.fetchUuidsWithSdpResult = fetchUuidsWithSdpResult;
  }

  /**
   * Overrides behavior of {@link BluetoothDevice#fetchUuidsWithSdp}. This method updates the
   * counter which counts the number of invocations of this method.
   *
   * @returns Value set by calling {@link ShadowBluetoothDevice#setFetchUuidsWithSdpResult}. If not
   *     previously set, will return false by default.
   */
  @Implementation
  protected boolean fetchUuidsWithSdp() {
    fetchUuidsWithSdpCount++;
    return fetchUuidsWithSdpResult;
  }

  /** Returns the number of times fetchUuidsWithSdp has been called. */
  public int getFetchUuidsWithSdpCount() {
    return fetchUuidsWithSdpCount;
  }

  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected BluetoothGatt connectGatt(
      Context context, boolean autoConnect, BluetoothGattCallback callback) {
    return connectGatt(callback);
  }

  @Implementation(minSdk = M)
  protected BluetoothGatt connectGatt(
      Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) {
    return connectGatt(callback);
  }

  @Implementation(minSdk = O)
  protected BluetoothGatt connectGatt(
      Context context,
      boolean autoConnect,
      BluetoothGattCallback callback,
      int transport,
      int phy,
      Handler handler) {
    return connectGatt(callback);
  }

  private BluetoothGatt connectGatt(BluetoothGattCallback callback) {
    BluetoothGatt bluetoothGatt = ShadowBluetoothGatt.newInstance(realBluetoothDevice);
    bluetoothGatts.add(bluetoothGatt);
    ShadowBluetoothGatt shadowBluetoothGatt = Shadow.extract(bluetoothGatt);
    shadowBluetoothGatt.setGattCallback(callback);
    return bluetoothGatt;
  }

  /**
   * Returns all {@link BluetoothGatt} objects created by calling {@link
   * ShadowBluetoothDevice#connectGatt}.
   */
  public List<BluetoothGatt> getBluetoothGatts() {
    return bluetoothGatts;
  }

  /**
   * Causes {@link BluetoothGattCallback#onConnectionStateChange to be called for every GATT client.
   * @param status Status of the GATT operation
   * @param newState The new state of the GATT profile
   */
  public void simulateGattConnectionChange(int status, int newState) {
    for (BluetoothGatt bluetoothGatt : bluetoothGatts) {
      ShadowBluetoothGatt shadowBluetoothGatt = Shadow.extract(bluetoothGatt);
      BluetoothGattCallback gattCallback = shadowBluetoothGatt.getGattCallback();
      gattCallback.onConnectionStateChange(bluetoothGatt, status, newState);
    }
  }
}


package org.robolectric.internal.bytecode;

import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;

import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import javax.inject.Inject;
import org.robolectric.util.Logger;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Util;

/**
 * Class loader that modifies the bytecode of Android classes to insert calls to Robolectric's
 * shadow classes.
 */
public class SandboxClassLoader extends URLClassLoader {
  private final ClassLoader erstwhileClassLoader;
  private final InstrumentationConfiguration config;
  private final ResourceProvider resourceProvider;
  private final ClassInstrumentor classInstrumentor;
  private final ClassNodeProvider classNodeProvider;

  /** Constructor for use by tests. */
  SandboxClassLoader(InstrumentationConfiguration config) {
    this(config, new UrlResourceProvider(), new OldClassInstrumentor(new ShadowDecorator()));
  }

  @Inject
  public SandboxClassLoader(
      InstrumentationConfiguration config, ResourceProvider resourceProvider,
      ClassInstrumentor classInstrumentor) {
    this(Thread.currentThread().getContextClassLoader(), config, resourceProvider,
        classInstrumentor);
  }

  public SandboxClassLoader(
      ClassLoader erstwhileClassLoader, InstrumentationConfiguration config,
      ResourceProvider resourceProvider, ClassInstrumentor classInstrumentor) {
    super(getClassPathUrls(erstwhileClassLoader), erstwhileClassLoader.getParent());
    this.erstwhileClassLoader = erstwhileClassLoader;

    this.config = config;
    this.resourceProvider = resourceProvider;

    this.classInstrumentor = classInstrumentor;

    classNodeProvider = new ClassNodeProvider() {
      @Override
      protected byte[] getClassBytes(String internalClassName) throws ClassNotFoundException {
        return getByteCode(internalClassName);
      }
    };
  }

  private static URL[] getClassPathUrls(ClassLoader classloader) {
    if (classloader instanceof URLClassLoader) {
      return ((URLClassLoader) classloader).getURLs();
    }
    return parseJavaClassPath();
  }

  // TODO(b/65488446): Use a public API once one is available.
  private static URL[] parseJavaClassPath() {
    ImmutableList.Builder<URL> urls = ImmutableList.builder();
    for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
      try {
        try {
          urls.add(new File(entry).toURI().toURL());
        } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
          urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
        }
      } catch (MalformedURLException e) {
        Logger.strict("malformed classpath entry: " + entry, e);
      }
    }
    return urls.build().toArray(new URL[0]);
  }

  @Override
  public URL getResource(String name) {
    if (config.shouldAcquireResource(name)) {
      return resourceProvider.getResource(name);
    }
    URL fromParent = super.getResource(name);
    if (fromParent != null) {
      return fromParent;
    }
    return resourceProvider.getResource(name);
  }

  private InputStream getClassBytesAsStreamPreferringLocalUrls(String resName) {
    InputStream fromUrlsClassLoader = resourceProvider.getResourceAsStream(resName);
    if (fromUrlsClassLoader != null) {
      return fromUrlsClassLoader;
    }
    return super.getResourceAsStream(resName);
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (config.shouldAcquire(name)) {
      return PerfStatsCollector.getInstance().measure("load sandboxed class",
          () -> maybeInstrumentClass(name));
    } else {
      return erstwhileClassLoader.loadClass(name);
    }
  }

  protected Class<?> maybeInstrumentClass(String className) throws ClassNotFoundException {
    final byte[] origClassBytes = getByteCode(className);

    MutableClass mutableClass = PerfStatsCollector.getInstance().measure("analyze class",
        () -> classInstrumentor.analyzeClass(origClassBytes, config, classNodeProvider)
    );

    try {
      final byte[] bytes;
      if (config.shouldInstrument(mutableClass)) {
        bytes = PerfStatsCollector.getInstance().measure("instrument class",
            () -> classInstrumentor.instrumentToBytes(mutableClass)
        );
      } else {
        bytes = postProcessUninstrumentedClass(mutableClass, origClassBytes);
      }
      ensurePackage(className);
      return defineClass(className, bytes, 0, bytes.length);
    } catch (Exception e) {
      throw new ClassNotFoundException("couldn't load " + className, e);
    } catch (OutOfMemoryError e) {
      System.err.println("[ERROR] couldn't load " + className + " in " + this);
      throw e;
    }
  }

  protected byte[] postProcessUninstrumentedClass(
      MutableClass mutableClass, byte[] origClassBytes) {
    return origClassBytes;
  }

  @Override
  protected Package getPackage(String name) {
    Package aPackage = super.getPackage(name);
    if (aPackage != null) {
      return aPackage;
    }

    return ReflectionHelpers.callInstanceMethod(erstwhileClassLoader, "getPackage",
        from(String.class, name));
  }

  protected byte[] getByteCode(String className) throws ClassNotFoundException {
    String classFilename = className.replace('.', '/') + ".class";
    try (InputStream classBytesStream = getClassBytesAsStreamPreferringLocalUrls(classFilename)) {
      if (classBytesStream == null) {
        throw new ClassNotFoundException(className);
      }

      return Util.readBytes(classBytesStream);
    } catch (IOException e) {
      throw new ClassNotFoundException("couldn't load " + className, e);
    }
  }

  private void ensurePackage(final String className) {
    int lastDotIndex = className.lastIndexOf('.');
    if (lastDotIndex != -1) {
      String pckgName = className.substring(0, lastDotIndex);
      Package pckg = getPackage(pckgName);
      if (pckg == null) {
        definePackage(pckgName, null, null, null, null, null, null, null);
      }
    }
  }

}


package org.robolectric.shadows;

import static org.robolectric.shadow.api.Shadow.directlyOn;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
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;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(BitmapDrawable.class)
public class ShadowBitmapDrawable extends ShadowDrawable {
  private ColorFilter colorFilter;
  String drawableCreateFromStreamSource;
  String drawableCreateFromPath;

  @RealObject private BitmapDrawable realBitmapDrawable;

  /**
   * Draws the contained bitmap onto the canvas at 0,0 with a default {@code Paint}
   *
   * @param canvas the canvas to draw on
   */
  @Implementation
  protected void draw(Canvas canvas) {
    Paint paint = new Paint();
    paint.setColorFilter(colorFilter);
    canvas.drawBitmap(realBitmapDrawable.getBitmap(), 0, 0, paint);
  }

  @Implementation
  protected Drawable mutate() {
    Bitmap bitmap = realBitmapDrawable.getBitmap();
    BitmapDrawable real = ReflectionHelpers.callConstructor(BitmapDrawable.class, ClassParameter.from(Bitmap.class, bitmap));
    ShadowBitmapDrawable shadow = Shadow.extract(real);
    shadow.colorFilter = this.colorFilter;
    shadow.drawableCreateFromStreamSource = drawableCreateFromStreamSource;
    return real;
  }

  @Implementation
  protected void setColorFilter(ColorFilter colorFilter) {
    this.colorFilter = colorFilter;
    directlyOn(realBitmapDrawable, BitmapDrawable.class).setColorFilter(colorFilter);
  }

  /**
   * Returns the resource id that this {@code BitmapDrawable} was loaded from. This lets
   * your tests assert that the bitmap is correct without having to actually load the bitmap.
   *
   * @return resource id from which this {@code BitmapDrawable} was loaded
   * @deprecated use ShadowBitmap#getCreatedFromResId() instead.
   */
  @Deprecated
  @Override
  public int getCreatedFromResId() {
    ShadowBitmap shadowBitmap = Shadow.extract(realBitmapDrawable.getBitmap());
    return shadowBitmap.getCreatedFromResId();
  }

  public String getSource() {
    return drawableCreateFromStreamSource;
  }

  public String getPath() {
    return drawableCreateFromPath;
  }
}


package org.robolectric.util.reflector;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import sun.misc.Unsafe;

/** Access to sun.misc.Unsafe and the various scary things within. */
@SuppressWarnings("NewApi")
public class UnsafeAccess {

  private static final Danger DANGER =
      getJavaVersion() < 11
          ? new DangerPre11()
          : new Danger11Plus();

  interface Danger {
    <T> Class<?> defineClass(Class<T> iClass, String reflectorClassName, byte[] bytecode);
  }

  static <T> Class<?> defineClass(Class<T> iClass, String reflectorClassName, byte[] bytecode) {
    return DANGER.defineClass(iClass, reflectorClassName, bytecode);
  }

  private static class DangerPre11 implements Danger {
    private final Unsafe unsafe;

    {
      try {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        unsafe = (Unsafe) unsafeField.get(null);
      } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new AssertionError(e);
      }
    }

    @Override
    public <T> Class<?> defineClass(Class<T> iClass, String reflectorClassName, byte[] bytecode) {
      return unsafe.defineClass(
          reflectorClassName, bytecode, 0, bytecode.length, iClass.getClassLoader(), null);
    }
  }

  private static class Danger11Plus implements Danger {
    private final Method privateLookupInMethod;
    private final Method defineClassMethod;

    {
      try {
        privateLookupInMethod =
            MethodHandles.class.getMethod(
                "privateLookupIn", Class.class, MethodHandles.Lookup.class);
        defineClassMethod =
            MethodHandles.Lookup.class.getMethod("defineClass", byte[].class);
      } catch (NoSuchMethodException e) {
        throw new AssertionError(e);
      }
    }

    @Override
    public <T> Class<?> defineClass(Class<T> iClass, String reflectorClassName, byte[] bytecode) {
      MethodHandles.Lookup lookup = MethodHandles.lookup();

      try {
        // MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(iClass, lookup);
        MethodHandles.Lookup privateLookup =
            (Lookup) privateLookupInMethod.invoke(lookup, iClass, lookup);

        // return privateLookup.defineClass(bytecode);
        return (Class<?>) defineClassMethod.invoke(privateLookup, bytecode);
      } catch (IllegalAccessException | InvocationTargetException e) {
        throw new AssertionError(e);
      }
    }
  }

  /**
   * Returns the Java version as an int value.
   *
   * @return the Java version as an int value (8, 9, etc.)
   */
  private 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));
  }
}


package org.robolectric.shadows;

import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import java.io.PrintStream;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(ViewGroup.class)
public class ShadowViewGroup extends ShadowView {
  @RealObject protected ViewGroup realViewGroup;

  private boolean disallowInterceptTouchEvent = false;
  private MotionEvent interceptedTouchEvent;

  @Implementation
  protected void addView(final View child, final int index, final ViewGroup.LayoutParams params) {
    Runnable addViewRunnable = () -> {
      directlyOn(realViewGroup, ViewGroup.class, "addView",
          ClassParameter.from(View.class, child),
          ClassParameter.from(int.class, index),
          ClassParameter.from(ViewGroup.LayoutParams.class, params));
    };
    if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
      addViewRunnable.run();
    } else {
      shadowMainLooper().runPaused(addViewRunnable);
    }
  }

  /**
   * Returns a string representation of this {@code ViewGroup} by concatenating all of the
   * strings contained in all of the descendants of this {@code ViewGroup}.
   */
  @Override
  public String innerText() {
    StringBuilder innerText = new StringBuilder();
    String delimiter = "";

    for (int i = 0; i < realViewGroup.getChildCount(); i++) {
      View child = realViewGroup.getChildAt(i);
      ShadowView shadowView = Shadow.extract(child);
      String childText = shadowView.innerText();
      if (childText.length() > 0) {
        innerText.append(delimiter);
        delimiter = " ";
      }
      innerText.append(childText);
    }
    return innerText.toString();
  }

  /**
   * Dumps the state of this {@code ViewGroup} to {@code System.out}.
   * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)}
   */
  @Override
  @Deprecated
  public void dump(PrintStream out, int indent) {
    dumpFirstPart(out, indent);
    if (realViewGroup.getChildCount() > 0) {
      out.println(">");

      for (int i = 0; i < realViewGroup.getChildCount(); i++) {
        View child = realViewGroup.getChildAt(i);
        ShadowView shadowChild = Shadow.extract(child);
        shadowChild.dump(out, indent + 2);
      }

      dumpIndent(out, indent);
      out.println("</" + realView.getClass().getSimpleName() + ">");
    } else {
      out.println("/>");
    }
  }

  @Implementation
  protected void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    disallowInterceptTouchEvent = disallowIntercept;
  }

  public boolean getDisallowInterceptTouchEvent() {
    return disallowInterceptTouchEvent;
  }

  protected void removedChild(View child) {
    if (isAttachedToWindow()) {
      ShadowView shadowView = Shadow.extract(child);
      shadowView.callOnDetachedFromWindow();
    }
  }

  public MotionEvent getInterceptedTouchEvent() {
    return interceptedTouchEvent;
  }

  @Implementation
  protected boolean onInterceptTouchEvent(MotionEvent ev) {
    interceptedTouchEvent = ev;
    return false;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.P;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_ORIENTATION;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_PRESSURE;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_SIZE;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MAJOR;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MINOR;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MAJOR;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MINOR;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_X;
import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_Y;

import android.graphics.Matrix;
import android.os.Parcel;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.res.android.NativeObjRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

/**
 * Shadow of MotionEvent.
 *
 * The Android framework stores motion events in a pool of native objects. All motion event data
 * is stored natively, and accessed via a series of static native methods following the pattern
 * nativeGetXXXX(mNativePtr, ...)
 *
 * This shadow mirrors this design, but has java equivalents of each native object. Most of the
 * contents of this class were transliterated from oreo-mr1 (SDK 27)
 * frameworks/base/core/jni/android_view_MotionEvent.cpp
 *
 * @see core/jni/android_view_MotionEvent.cpp
 *
 * Tests should not reference this class directly. MotionEvents should be created via one of the
 * MotionEvent.obtain methods or via MotionEventBuilder.
 */
@SuppressWarnings({"UnusedDeclaration"})
@Implements(MotionEvent.class)
public class ShadowMotionEvent {

  private static NativeObjRegistry<NativeInput.MotionEvent> nativeMotionEventRegistry =
      new NativeObjRegistry<>(NativeInput.MotionEvent.class);

  private static final int HISTORY_CURRENT = -0x80000000;

  @RealObject private MotionEvent realMotionEvent;

  @Resetter
  public static void reset() {
    // rely on MotionEvent finalizer to clear native object instead of calling
    // nativeMotionEventRegistry.clear();
    ReflectionHelpers.setStaticField(MotionEvent.class, "gRecyclerTop", null);
    ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerCoords", null);
    ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerProperties", null);
    ReflectionHelpers.setStaticField(MotionEvent.class, "gRecyclerUsed", 0);
    ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerIndexMap", null);
  }

  private static void validatePointerCount(int pointerCount) {
    checkState(pointerCount >= 1, "pointerCount must be at least 1");
  }

  private static void validatePointerPropertiesArray(
      PointerProperties[] pointerPropertiesObjArray, int pointerCount) {
    checkNotNull(pointerPropertiesObjArray, "pointerProperties array must not be null");
    checkState(
        pointerPropertiesObjArray.length >= pointerCount,
        "pointerProperties array must be large enough to hold all pointers");
  }

  private static void validatePointerCoordsObjArray(
      PointerCoords[] pointerCoordsObjArray, int pointerCount) {
    checkNotNull(pointerCoordsObjArray, "pointerCoords array must not be null");
    checkState(
        pointerCoordsObjArray.length >= pointerCount,
        "pointerCoords array must be large enough to hold all pointers");
  }

  private static void validatePointerIndex(int pointerIndex, int pointerCount) {
    checkState(pointerIndex >= 0 && pointerIndex < pointerCount, "pointerIndex out of range");
  }

  private static void validateHistoryPos(int historyPos, int historySize) {
    checkState(historyPos >= 0 && historyPos < historySize, "historyPos out of range");
  }

  private static void validatePointerCoords(PointerCoords pointerCoordsObj) {
    checkNotNull(pointerCoordsObj, "pointerCoords must not be null");
  }

  private static void validatePointerProperties(PointerProperties pointerPropertiesObj) {
    checkNotNull(pointerPropertiesObj, "pointerProperties must not be null");
  }

  private static NativeInput.PointerCoords pointerCoordsToNative(
      PointerCoords pointerCoordsObj, float xOffset, float yOffset) {
    NativeInput.PointerCoords outRawPointerCoords = new NativeInput.PointerCoords();
    outRawPointerCoords.clear();
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoordsObj.x - xOffset);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoordsObj.y - yOffset);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerCoordsObj.pressure);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerCoordsObj.size);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerCoordsObj.touchMajor);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerCoordsObj.touchMinor);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerCoordsObj.toolMajor);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerCoordsObj.toolMinor);
    outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerCoordsObj.orientation);
    long packedAxisBits = ReflectionHelpers.getField(pointerCoordsObj, "mPackedAxisBits");
    NativeBitSet64 bits = new NativeBitSet64(packedAxisBits);
    if (!bits.isEmpty()) {
      float[] valuesArray = ReflectionHelpers.getField(pointerCoordsObj, "mPackedAxisValues");
      if (valuesArray != null) {
        int index = 0;
        do {
          int axis = bits.clearFirstMarkedBit();
          outRawPointerCoords.setAxisValue(axis, valuesArray[index++]);
        } while (!bits.isEmpty());
      }
    }
    return outRawPointerCoords;
  }

  private static float[] obtainPackedAxisValuesArray(
      int minSize, PointerCoords outPointerCoordsObj) {
    float[] outValuesArray = ReflectionHelpers.getField(outPointerCoordsObj, "mPackedAxisValues");
    if (outValuesArray != null) {
      int size = outValuesArray.length;
      if (minSize <= size) {
        return outValuesArray;
      }
    }
    int size = 8;
    while (size < minSize) {
      size *= 2;
    }
    outValuesArray = new float[size];
    ReflectionHelpers.setField(outPointerCoordsObj, "mPackedAxisValues", outValuesArray);
    return outValuesArray;
  }

  private static void pointerCoordsFromNative(
      NativeInput.PointerCoords rawPointerCoords,
      float xOffset,
      float yOffset,
      PointerCoords outPointerCoordsObj) {
    outPointerCoordsObj.x = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_X) + xOffset;
    outPointerCoordsObj.y = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_Y) + yOffset;
    outPointerCoordsObj.pressure = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
    outPointerCoordsObj.size = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_SIZE);
    outPointerCoordsObj.touchMajor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
    outPointerCoordsObj.touchMinor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
    outPointerCoordsObj.toolMajor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR);
    outPointerCoordsObj.toolMinor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR);
    outPointerCoordsObj.orientation = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
    long outBits = 0;
    NativeBitSet64 bits = new NativeBitSet64(rawPointerCoords.getBits());
    bits.clearBit(AMOTION_EVENT_AXIS_X);
    bits.clearBit(AMOTION_EVENT_AXIS_Y);
    bits.clearBit(AMOTION_EVENT_AXIS_PRESSURE);
    bits.clearBit(AMOTION_EVENT_AXIS_SIZE);
    bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
    bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MINOR);
    bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR);
    bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR);
    bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION);
    if (!bits.isEmpty()) {
      int packedAxesCount = bits.count();
      float[] outValuesArray = obtainPackedAxisValuesArray(packedAxesCount, outPointerCoordsObj);
      float[] outValues = outValuesArray;
      int index = 0;
      do {
        int axis = bits.clearFirstMarkedBit();
        outBits |= NativeBitSet64.valueForBit(axis);
        outValues[index++] = rawPointerCoords.getAxisValue(axis);
      } while (!bits.isEmpty());
    }
    ReflectionHelpers.setField(outPointerCoordsObj, "mPackedAxisBits", outBits);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeInitialize(
      int nativePtr,
      int deviceId,
      int source,
      int action,
      int flags,
      int edgeFlags,
      int metaState,
      int buttonState,
      float xOffset,
      float yOffset,
      float xPrecision,
      float yPrecision,
      long downTimeNanos,
      long eventTimeNanos,
      int pointerCount,
      PointerProperties[] pointerIds,
      PointerCoords[] pointerCoords) {
    return (int)
        nativeInitialize(
            (long) nativePtr,
            deviceId,
            source,
            action,
            flags,
            edgeFlags,
            metaState,
            buttonState,
            xOffset,
            yOffset,
            xPrecision,
            yPrecision,
            downTimeNanos,
            eventTimeNanos,
            pointerCount,
            pointerIds,
            pointerCoords);
  }

  @Implementation(minSdk = LOLLIPOP, maxSdk = P)
  @HiddenApi
  protected static long nativeInitialize(
      long nativePtr,
      int deviceId,
      int source,
      int action,
      int flags,
      int edgeFlags,
      int metaState,
      int buttonState,
      float xOffset,
      float yOffset,
      float xPrecision,
      float yPrecision,
      long downTimeNanos,
      long eventTimeNanos,
      int pointerCount,
      PointerProperties[] pointerPropertiesObjArray,
      PointerCoords[] pointerCoordsObjArray) {

    validatePointerCount(pointerCount);
    validatePointerPropertiesArray(pointerPropertiesObjArray, pointerCount);
    validatePointerCoordsObjArray(pointerCoordsObjArray, pointerCount);

    NativeInput.MotionEvent event;
    if (nativePtr > 0) {
      event = nativeMotionEventRegistry.getNativeObject(nativePtr);
    } else {
      event = new NativeInput.MotionEvent();
      nativePtr = nativeMotionEventRegistry.register(event);
    }

    NativeInput.PointerCoords[] rawPointerCoords = new NativeInput.PointerCoords[pointerCount];
    for (int i = 0; i < pointerCount; i++) {
      PointerCoords pointerCoordsObj = pointerCoordsObjArray[i];
      checkNotNull(pointerCoordsObj);
      rawPointerCoords[i] = pointerCoordsToNative(pointerCoordsObj, xOffset, yOffset);
    }

    event.initialize(
        deviceId,
        source,
        action,
        0,
        flags,
        edgeFlags,
        metaState,
        buttonState,
        xOffset,
        yOffset,
        xPrecision,
        yPrecision,
        downTimeNanos,
        eventTimeNanos,
        pointerCount,
        pointerPropertiesObjArray,
        rawPointerCoords);
    return nativePtr;
  }

  // TODO(brettchabot): properly handle displayId
  @Implementation(minSdk = android.os.Build.VERSION_CODES.Q)
  @HiddenApi
  protected static long nativeInitialize(
      long nativePtr,
      int deviceId,
      int source,
      int displayId,
      int action,
      int flags,
      int edgeFlags,
      int metaState,
      int buttonState,
      int classification,
      float xOffset,
      float yOffset,
      float xPrecision,
      float yPrecision,
      long downTimeNanos,
      long eventTimeNanos,
      int pointerCount,
      PointerProperties[] pointerIds,
      PointerCoords[] pointerCoords) {
        return
        nativeInitialize(
            nativePtr,
            deviceId,
            source,
            action,
            flags,
            edgeFlags,
            metaState,
            buttonState,
            xOffset,
            yOffset,
            xPrecision,
            yPrecision,
            downTimeNanos,
            eventTimeNanos,
            pointerCount,
            pointerIds,
            pointerCoords);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeDispose(int nativePtr) {
    nativeDispose((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeDispose(long nativePtr) {
    nativeMotionEventRegistry.unregister(nativePtr);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeAddBatch(
      int nativePtr, long eventTimeNanos, PointerCoords[] pointerCoordsObjArray, int metaState) {
    nativeAddBatch((long) nativePtr, eventTimeNanos, pointerCoordsObjArray, metaState);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeAddBatch(
      long nativePtr, long eventTimeNanos, PointerCoords[] pointerCoordsObjArray, int metaState) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerCoordsObjArray(pointerCoordsObjArray, pointerCount);
    NativeInput.PointerCoords[] rawPointerCoords = new NativeInput.PointerCoords[pointerCount];
    for (int i = 0; i < pointerCount; i++) {
      PointerCoords pointerCoordsObj = pointerCoordsObjArray[i];
      checkNotNull(pointerCoordsObj);
      rawPointerCoords[i] =
          pointerCoordsToNative(pointerCoordsObj, event.getXOffset(), event.getYOffset());
    }
    event.addSample(eventTimeNanos, rawPointerCoords);
    event.setMetaState(event.getMetaState() | metaState);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeGetPointerCoords(
      int nativePtr, int pointerIndex, int historyPos, PointerCoords outPointerCoordsObj) {
    nativeGetPointerCoords((long) nativePtr, pointerIndex, historyPos, outPointerCoordsObj);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeGetPointerCoords(
      long nativePtr, int pointerIndex, int historyPos, PointerCoords outPointerCoordsObj) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerIndex(pointerIndex, pointerCount);
    validatePointerCoords(outPointerCoordsObj);

    NativeInput.PointerCoords rawPointerCoords;
    if (historyPos == HISTORY_CURRENT) {
      rawPointerCoords = event.getRawPointerCoords(pointerIndex);
    } else {
      int historySize = event.getHistorySize();
      validateHistoryPos(historyPos, historySize);
      rawPointerCoords = event.getHistoricalRawPointerCoords(pointerIndex, historyPos);
    }
    pointerCoordsFromNative(
        rawPointerCoords, event.getXOffset(), event.getYOffset(), outPointerCoordsObj);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeGetPointerProperties(
      int nativePtr, int pointerIndex, PointerProperties outPointerPropertiesObj) {
    nativeGetPointerProperties((long) nativePtr, pointerIndex, outPointerPropertiesObj);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeGetPointerProperties(
      long nativePtr, int pointerIndex, PointerProperties outPointerPropertiesObj) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerIndex(pointerIndex, pointerCount);
    validatePointerProperties(outPointerPropertiesObj);

    PointerProperties pointerProperties = event.getPointerProperties(pointerIndex);
    // pointerPropertiesFromNative(env, pointerProperties, outPointerPropertiesObj);
    outPointerPropertiesObj.copyFrom(pointerProperties);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeReadFromParcel(int nativePtr, Parcel parcelObj) {
    return (int) nativeReadFromParcel((long) nativePtr, parcelObj);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static long nativeReadFromParcel(long nativePtr, Parcel parcelObj) {
    NativeInput.MotionEvent event;
    if (nativePtr == 0) {
      event = new NativeInput.MotionEvent();
      nativePtr = nativeMotionEventRegistry.register(event);
    } else {
      event = nativeMotionEventRegistry.getNativeObject(nativePtr);
    }
    boolean status = event.readFromParcel(parcelObj);
    if (!status) {
      if (nativePtr > 0) {
        nativeMotionEventRegistry.unregister(nativePtr);
      }
      throw new RuntimeException("Failed to read MotionEvent parcel.");
    }
    return nativePtr;
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeWriteToParcel(int nativePtr, Parcel parcel) {
    nativeWriteToParcel((long) nativePtr, parcel);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeWriteToParcel(long nativePtr, Parcel parcel) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    if (!event.writeToParcel(parcel)) {
      throw new RuntimeException("Failed to write MotionEvent parcel.");
    }
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static String nativeAxisToString(int axis) {
    // The native code just mirrors the AXIS_* constants defined in MotionEvent.java.
    // Look up the field value by reflection to future proof this method
    for (Field field : MotionEvent.class.getDeclaredFields()) {
      int modifiers = field.getModifiers();
      try {
        if (Modifier.isStatic(modifiers)
            && Modifier.isPublic(modifiers)
            && field.getName().startsWith("AXIS_")
            && field.getInt(null) == axis) {
          // return the field name stripping off the "AXIS_" prefix
          return field.getName().substring(5);
        }
      } catch (IllegalAccessException e) {
        // ignore
      }
    }
    return null;
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeAxisFromString(String label) {
    // The native code just mirrors the AXIS_* constants defined in MotionEvent.java. Look up
    // the field value by reflection
    try {
      Field constantField = MotionEvent.class.getDeclaredField("AXIS_" + label);
      return constantField.getInt(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
      return 0;
    }
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetPointerId(int nativePtr, int pointerIndex) {
    return nativeGetPointerId((long) nativePtr, pointerIndex);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetPointerId(long nativePtr, int pointerIndex) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerIndex(pointerIndex, pointerCount);
    return event.getPointerId(pointerIndex);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetToolType(int nativePtr, int pointerIndex) {
    return nativeGetToolType((long) nativePtr, pointerIndex);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetToolType(long nativePtr, int pointerIndex) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerIndex(pointerIndex, pointerCount);
    return event.getToolType(pointerIndex);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static long nativeGetEventTimeNanos(int nativePtr, int historyPos) {
    return nativeGetEventTimeNanos((long) nativePtr, historyPos);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static long nativeGetEventTimeNanos(long nativePtr, int historyPos) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    if (historyPos == HISTORY_CURRENT) {
      return event.getEventTime();
    } else {
      int historySize = event.getHistorySize();
      validateHistoryPos(historyPos, historySize);
      return event.getHistoricalEventTime(historyPos);
    }
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static float nativeGetRawAxisValue(
      int nativePtr, int axis, int pointerIndex, int historyPos) {
    return nativeGetRawAxisValue((long) nativePtr, axis, pointerIndex, historyPos);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static float nativeGetRawAxisValue(
      long nativePtr, int axis, int pointerIndex, int historyPos) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerIndex(pointerIndex, pointerCount);

    if (historyPos == HISTORY_CURRENT) {
      return event.getRawAxisValue(axis, pointerIndex);
    } else {
      int historySize = event.getHistorySize();
      validateHistoryPos(historyPos, historySize);
      return event.getHistoricalRawAxisValue(axis, pointerIndex, historyPos);
    }
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static float nativeGetAxisValue(
      int nativePtr, int axis, int pointerIndex, int historyPos) {
    return nativeGetAxisValue((long) nativePtr, axis, pointerIndex, historyPos);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static float nativeGetAxisValue(
      long nativePtr, int axis, int pointerIndex, int historyPos) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    int pointerCount = event.getPointerCount();
    validatePointerIndex(pointerIndex, pointerCount);

    if (historyPos == HISTORY_CURRENT) {
      return event.getAxisValue(axis, pointerIndex);
    } else {
      int historySize = event.getHistorySize();
      validateHistoryPos(historyPos, historySize);
      return event.getHistoricalAxisValue(axis, pointerIndex, historyPos);
    }
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeCopy(int destNativePtr, int sourceNativePtr, boolean keepHistory) {
    return (int) nativeCopy((long) destNativePtr, (long) sourceNativePtr, keepHistory);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static long nativeCopy(long destNativePtr, long sourceNativePtr, boolean keepHistory) {
    NativeInput.MotionEvent destEvent = nativeMotionEventRegistry.peekNativeObject(destNativePtr);
    if (destEvent == null) {
      destEvent = new NativeInput.MotionEvent();
      destNativePtr = nativeMotionEventRegistry.register(destEvent);
    }
    NativeInput.MotionEvent sourceEvent = getNativeMotionEvent(sourceNativePtr);
    destEvent.copyFrom(sourceEvent, keepHistory);
    return destNativePtr;
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetDeviceId(int nativePtr) {
    return nativeGetDeviceId((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetDeviceId(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getDeviceId();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetSource(int nativePtr) {
    return nativeGetSource((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetSource(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getSource();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeSetSource(int nativePtr, int source) {
    nativeSetSource((long) nativePtr, source);
    return 0;
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  @SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
  protected static void nativeSetSource(long nativePtr, int source) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setSource(source);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetAction(int nativePtr) {
    return nativeGetAction((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetAction(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getAction();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeSetAction(int nativePtr, int action) {
    nativeSetAction((long) nativePtr, action);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeSetAction(long nativePtr, int action) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setAction(action);
  }

  @Implementation(minSdk = M)
  @HiddenApi
  protected static int nativeGetActionButton(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getActionButton();
  }

  @Implementation(minSdk = M)
  @HiddenApi
  protected static void nativeSetActionButton(long nativePtr, int button) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setActionButton(button);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static boolean nativeIsTouchEvent(int nativePtr) {
    return nativeIsTouchEvent((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static boolean nativeIsTouchEvent(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.isTouchEvent();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetFlags(int nativePtr) {
    return nativeGetFlags((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetFlags(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getFlags();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeSetFlags(int nativePtr, int flags) {
    nativeSetFlags((long) nativePtr, flags);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeSetFlags(long nativePtr, int flags) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setFlags(flags);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetEdgeFlags(int nativePtr) {
    return nativeGetEdgeFlags((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetEdgeFlags(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getEdgeFlags();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeSetEdgeFlags(int nativePtr, int edgeFlags) {
    nativeSetEdgeFlags((long) nativePtr, edgeFlags);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeSetEdgeFlags(long nativePtr, int edgeFlags) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setEdgeFlags(edgeFlags);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetMetaState(int nativePtr) {
    return nativeGetMetaState((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetMetaState(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getMetaState();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetButtonState(int nativePtr) {
    return nativeGetButtonState((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetButtonState(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getButtonState();
  }

  @Implementation(minSdk = M)
  @HiddenApi
  protected static void nativeSetButtonState(long nativePtr, int buttonState) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setButtonState(buttonState);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeOffsetLocation(int nativePtr, float deltaX, float deltaY) {
    nativeOffsetLocation((long) nativePtr, deltaX, deltaY);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.offsetLocation(deltaX, deltaY);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static float nativeGetXOffset(int nativePtr) {
    return nativeGetXOffset((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static float nativeGetXOffset(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getXOffset();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static float nativeGetYOffset(int nativePtr) {
    return nativeGetYOffset((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static float nativeGetYOffset(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getYOffset();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static float nativeGetXPrecision(int nativePtr) {
    return nativeGetXPrecision((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static float nativeGetXPrecision(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getXPrecision();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static float nativeGetYPrecision(int nativePtr) {
    return nativeGetYPrecision((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static float nativeGetYPrecision(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getYPrecision();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static long nativeGetDownTimeNanos(int nativePtr) {
    return nativeGetDownTimeNanos((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static long nativeGetDownTimeNanos(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getDownTime();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeSetDownTimeNanos(int nativePtr, long downTimeNanos) {
    nativeSetDownTimeNanos((long) nativePtr, downTimeNanos);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeSetDownTimeNanos(long nativePtr, long downTimeNanos) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.setDownTime(downTimeNanos);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetPointerCount(int nativePtr) {
    return nativeGetPointerCount((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetPointerCount(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getPointerCount();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeFindPointerIndex(int nativePtr, int pointerId) {
    return nativeFindPointerIndex((long) nativePtr, pointerId);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeFindPointerIndex(long nativePtr, int pointerId) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.findPointerIndex(pointerId);
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static int nativeGetHistorySize(int nativePtr) {
    return nativeGetHistorySize((long) nativePtr);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static int nativeGetHistorySize(long nativePtr) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    return event.getHistorySize();
  }

  @Implementation(maxSdk = KITKAT_WATCH)
  @HiddenApi
  protected static void nativeScale(int nativePtr, float scale) {
    nativeScale((long) nativePtr, scale);
  }

  @Implementation(minSdk = LOLLIPOP)
  @HiddenApi
  protected static void nativeScale(long nativePtr, float scale) {
    NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
    event.scale(scale);
  }

  private static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) {
    // check that MotionEvent was initialized properly. This can occur if MotionEvent was mocked
    checkState(
        nativePtr > 0,
        "MotionEvent has not been initialized. "
            + "Ensure MotionEvent.obtain was used to create it, instead of creating it directly "
            + "or via a Mocking framework");

    return nativeMotionEventRegistry.getNativeObject(nativePtr);
  }

  // shadow this directly as opposed to nativeTransform because need access to ShadowMatrix
  @Implementation
  protected final void transform(Matrix matrix) {
    checkNotNull(matrix);
    NativeInput.MotionEvent event = getNativeMotionEvent();
    ShadowMatrix shadowMatrix = Shadow.extract(matrix);

    float[] m = new float[9];
    shadowMatrix.getValues(m);
    event.transform(m);
  }

  private NativeInput.MotionEvent getNativeMotionEvent() {
    long nativePtr;
    if (RuntimeEnvironment.getApiLevel() <= KITKAT_WATCH) {
      Integer nativePtrInt = ReflectionHelpers.getField(realMotionEvent, "mNativePtr");
      nativePtr = nativePtrInt.longValue();
    } else {
      nativePtr = ReflectionHelpers.getField(realMotionEvent, "mNativePtr");
    }
    return nativeMotionEventRegistry.getNativeObject(nativePtr);
  }

  // Testing API methods

  /**
   * @deprecated use {@link MotionEvent#obtain} or {@link
   *     androidx.test.core.view.MotionEventBuilder} to create a MotionEvent with desired data.
   */
  @Deprecated
  public MotionEvent setPointer2(float pointer1X, float pointer1Y) {
    NativeInput.MotionEvent event = getNativeMotionEvent();
    List<NativeInput.PointerCoords> pointerCoords = event.getSamplePointerCoords();
    List<PointerProperties> pointerProperties = event.getPointerProperties();
    ensureTwoPointers(pointerCoords, pointerProperties);

    pointerCoords.get(1).setAxisValue(AMOTION_EVENT_AXIS_X, pointer1X);
    pointerCoords.get(1).setAxisValue(AMOTION_EVENT_AXIS_Y, pointer1Y);
    return realMotionEvent;
  }

  private static void ensureTwoPointers(
      List<NativeInput.PointerCoords> pointerCoords, List<PointerProperties> pointerProperties) {
    if (pointerCoords.size() < 2) {
      pointerCoords.add(new NativeInput.PointerCoords());
    }
    if (pointerProperties.size() < 2) {
      pointerProperties.add(new PointerProperties());
    }
  }

  /**
   * @deprecated use {@link MotionEvent#obtain} or {@link
   *     androidx.test.core.view.MotionEventBuilder#setPointerAction(int, int)} to create a
   *     MotionEvent with desired data.
   */
  @Deprecated
  public void setPointerIndex(int pointerIndex) {
    NativeInput.MotionEvent event = getNativeMotionEvent();
    // pointer index is stored in upper two bytes of action
    event.setAction(
        event.getAction() | ((pointerIndex & 0xff) << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
  }

  /**
   * @deprecated use {@link MotionEvent#obtain} or {@link MotionEventBuilder} to create a
   *     MotionEvent with desired data
   */
  @Deprecated
  public void setPointerIds(int index0PointerId, int index1PointerId) {
    NativeInput.MotionEvent event = getNativeMotionEvent();
    List<NativeInput.PointerCoords> pointerCoords = event.getSamplePointerCoords();
    List<PointerProperties> pointerProperties = event.getPointerProperties();
    ensureTwoPointers(pointerCoords, pointerProperties);

    pointerProperties.get(0).id = index0PointerId;
    pointerProperties.get(1).id = index1PointerId;
  }
}


package org.robolectric.shadows;

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

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Build;
import android.os.ParcelUuid;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

@Implements(value = BluetoothServerSocket.class)
public class ShadowBluetoothServerSocket {
  private final BlockingQueue<BluetoothSocket> sockets = new LinkedBlockingQueue<>();
  private boolean closed;

  @SuppressLint("PrivateApi")
  @SuppressWarnings("unchecked")
  public static BluetoothServerSocket newInstance(
      int type, boolean auth, boolean encrypt, ParcelUuid uuid) {
    if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
      return Shadow.newInstance(
          BluetoothServerSocket.class,
          new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, ParcelUuid.class},
          new Object[] {type, auth, encrypt, uuid});
    } else {
      return Shadow.newInstance(
          BluetoothServerSocket.class,
          new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, Integer.TYPE},
          new Object[] {type, auth, encrypt, getPort(uuid)});
    }
  }

  // Port ranges are valid from 1 to MAX_RFCOMM_CHANNEL.
  private static int getPort(ParcelUuid uuid) {
    return Math.abs(uuid.hashCode() % BluetoothSocket.MAX_RFCOMM_CHANNEL) + 1;
  }

  /**
   * May block the current thread and wait until {@link BluetoothDevice} is offered via
   * {@link #deviceConnected(BluetoothDevice)} method or timeout occurred.
   *
   * @return socket of the connected bluetooth device
   * @throws IOException if socket has been closed, thread interrupted while waiting or timeout has
   *         occurred.
   */
  @Implementation
  protected BluetoothSocket accept(int timeout) throws IOException {
    if (closed) {
      throw new IOException("Socket closed");
    }

    BluetoothSocket socket;
    try {
      socket = timeout == -1
              ? sockets.take() : sockets.poll(timeout, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
      throw new IOException(e);
    }

    if (socket == null) {
      throw new IOException("Timeout occurred");
    }
    socket.connect();
    return socket;
  }

  @Implementation
  protected void close() throws IOException {
    closed = true;
  }

  /** Creates {@link BluetoothSocket} for the given device and makes this socket available
   * immediately in the {@link #accept(int)} method. */
  public BluetoothSocket deviceConnected(BluetoothDevice device) {
    BluetoothSocket socket = Shadow.newInstanceOf(BluetoothSocket.class);
    ReflectionHelpers.setField(socket, "mDevice", device);
    sockets.offer(socket);
    return socket;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;

import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;

@Implements(value = SubscriptionManager.class, minSdk = LOLLIPOP_MR1)
public class ShadowSubscriptionManager {

  public static final int INVALID_PHONE_INDEX =
      ReflectionHelpers.getStaticField(SubscriptionManager.class, "INVALID_PHONE_INDEX");

  private static int defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;

  /** Returns value set with {@link #setDefaultSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultSubscriptionId() {
    return defaultSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultDataSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultDataSubscriptionId() {
    return defaultDataSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultSmsSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultSmsSubscriptionId() {
    return defaultSmsSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultVoiceSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultVoiceSubscriptionId() {
    return defaultVoiceSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultSubId() {
    return defaultSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultVoiceSubId() {
    return defaultVoiceSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultSmsSubId() {
    return defaultSmsSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultDataSubId() {
    return defaultDataSubscriptionId;
  }

  /** Sets the value that will be returned by {@link #getDefaultSubscriptionId()}. */
  public static void setDefaultSubscriptionId(int defaultSubscriptionId) {
    ShadowSubscriptionManager.defaultSubscriptionId = defaultSubscriptionId;
  }

  public static void setDefaultDataSubscriptionId(int defaultDataSubscriptionId) {
    ShadowSubscriptionManager.defaultDataSubscriptionId = defaultDataSubscriptionId;
  }

  public static void setDefaultSmsSubscriptionId(int defaultSmsSubscriptionId) {
    ShadowSubscriptionManager.defaultSmsSubscriptionId = defaultSmsSubscriptionId;
  }

  public static void setDefaultVoiceSubscriptionId(int defaultVoiceSubscriptionId) {
    ShadowSubscriptionManager.defaultVoiceSubscriptionId = defaultVoiceSubscriptionId;
  }

  /**
   * Cache of phone IDs used by {@link getPhoneId}. Managed by {@link putPhoneId} and {@link
   * removePhoneId}.
   */
  private static Map<Integer, Integer> phoneIds = new HashMap<>();

  /**
   * Cache of {@link SubscriptionInfo} used by {@link #getActiveSubscriptionInfoList}.
   * Managed by {@link #setActiveSubscriptionInfoList}.
   */
  private List<SubscriptionInfo> subscriptionList = new ArrayList<>();
  /**
   * Cache of {@link SubscriptionInfo} used by {@link #getAvailableSubscriptionInfoList}. Managed by
   * {@link #setAvailableSubscriptionInfos}.
   */
  private List<SubscriptionInfo> availableSubscriptionList = new ArrayList<>();
  /**
   * List of listeners to be notified if the list of {@link SubscriptionInfo} changes. Managed by
   * {@link #addOnSubscriptionsChangedListener} and {@link removeOnSubscriptionsChangedListener}.
   */
  private List<OnSubscriptionsChangedListener> listeners = new ArrayList<>();
  /**
   * Cache of subscription ids used by {@link #isNetworkRoaming}. Managed by {@link
   * #setNetworkRoamingStatus} and {@link #clearNetworkRoamingStatus}.
   */
  private Set<Integer> roamingSimSubscriptionIds = new HashSet<>();

  /**
   * Returns the active list of {@link SubscriptionInfo} that were set via {@link
   * #setActiveSubscriptionInfoList}.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected List<SubscriptionInfo> getActiveSubscriptionInfoList() {
    return subscriptionList;
  }

  /**
   * Returns the available list of {@link SubscriptionInfo} that were set via {@link
   * #setAvailableSubscriptionInfoList}.
   */
  @Implementation(minSdk = O_MR1)
  protected List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
    return availableSubscriptionList;
  }

  /**
   * Returns the size of the list of {@link SubscriptionInfo} that were set via {@link
   * #setActiveSubscriptionInfoList}. If no list was set, returns 0.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected int getActiveSubscriptionInfoCount() {
    return subscriptionList == null ? 0 : subscriptionList.size();
  }

  /**
   * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find
   * one with the specified id or null if none found.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected SubscriptionInfo getActiveSubscriptionInfo(int subId) {
    if (subscriptionList == null) {
      return null;
    }
    for (SubscriptionInfo info : subscriptionList) {
      if (info.getSubscriptionId() == subId) {
        return info;
      }
    }
    return null;
  }

  /**
   * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find
   * one with the specified slot index or null if none found.
   */
  @Implementation(minSdk = N)
  protected SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) {
    if (subscriptionList == null) {
      return null;
    }
    for (SubscriptionInfo info : subscriptionList) {
      if (info.getSimSlotIndex() == slotIndex) {
        return info;
      }
    }
    return null;
  }

  /**
   * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
   * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
   * @param list - The subscription info list, can be null.
   */
  public void setActiveSubscriptionInfoList(List<SubscriptionInfo> list) {
    subscriptionList = list;
    dispatchOnSubscriptionsChanged();
  }

  /**
   * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
   * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
   *
   * @param list - The subscription info list, can be null.
   */
  public void setAvailableSubscriptionInfoList(List<SubscriptionInfo> list) {
    availableSubscriptionList = list;
    dispatchOnSubscriptionsChanged();
  }

  /**
   * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
   * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
   */
  public void setActiveSubscriptionInfos(SubscriptionInfo... infos) {
    if (infos == null) {
      setActiveSubscriptionInfoList(ImmutableList.of());
    } else {
      setActiveSubscriptionInfoList(Arrays.asList(infos));
    }
  }

  /**
   * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
   * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
   */
  public void setAvailableSubscriptionInfos(SubscriptionInfo... infos) {
    if (infos == null) {
      setAvailableSubscriptionInfoList(ImmutableList.of());
    } else {
      setAvailableSubscriptionInfoList(Arrays.asList(infos));
    }
  }

  /**
   * Adds a listener to a local list of listeners. Will be triggered by {@link
   * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
    listeners.add(listener);
  }

  /**
   * Removes a listener from a local list of listeners. Will be triggered by {@link
   * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
    listeners.remove(listener);
  }

  /** Returns subscription Ids that were set via {@link #setActiveSubscriptionInfoList}. */
  @Implementation(minSdk = LOLLIPOP_MR1)
  @HiddenApi
  protected int[] getActiveSubscriptionIdList() {
    final List<SubscriptionInfo> infos = getActiveSubscriptionInfoList();
    if (infos == null) {
      return new int[0];
    }
    int[] ids = new int[infos.size()];
    for (int i = 0; i < infos.size(); i++) {
      ids[i] = infos.get(i).getSubscriptionId();
    }
    return ids;
  }

  /**
   * Notifies {@link OnSubscriptionsChangedListener} listeners that the list of {@link
   * SubscriptionInfo} has been updated.
   */
  private void dispatchOnSubscriptionsChanged() {
    for (OnSubscriptionsChangedListener listener : listeners) {
      listener.onSubscriptionsChanged();
    }
  }

  /** Clears the local cache of roaming subscription Ids used by {@link #isNetworkRoaming}. */
  public void clearNetworkRoamingStatus(){
    roamingSimSubscriptionIds.clear();
  }

  /**
   * If isNetworkRoaming is set, it will mark the provided sim subscriptionId as roaming in a local
   * cache. If isNetworkRoaming is unset it will remove the subscriptionId from the local cache. The
   * local cache is used to provide roaming status returned by {@link #isNetworkRoaming}.
   */
  public void setNetworkRoamingStatus(int simSubscriptionId, boolean isNetworkRoaming) {
    if (isNetworkRoaming) {
      roamingSimSubscriptionIds.add(simSubscriptionId);
    } else {
      roamingSimSubscriptionIds.remove(simSubscriptionId);
    }
  }

  /**
   * Uses the local cache of roaming sim subscription Ids managed by {@link
   * #setNetworkRoamingStatus} to return subscription Ids marked as roaming. Otherwise subscription
   * Ids will be considered as non-roaming if they are not in the cache.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected boolean isNetworkRoaming(int simSubscriptionId) {
    return roamingSimSubscriptionIds.contains(simSubscriptionId);
  }

  /** Adds a subscription ID-phone ID mapping to the map used by {@link getPhoneId}. */
  public static void putPhoneId(int subId, int phoneId) {
    phoneIds.put(subId, phoneId);
  }

  /**
   * Removes a subscription ID-phone ID mapping from the map used by {@link getPhoneId}.
   *
   * @return the previous phone ID associated with the subscription ID, or null if there was no
   *     mapping for the subscription ID
   */
  public static Integer removePhoneId(int subId) {
    return phoneIds.remove(subId);
  }

  /**
   * Removes all mappings between subscription IDs and phone IDs from the map used by {@link
   * getPhoneId}.
   */
  public static void clearPhoneIds() {
    phoneIds.clear();
  }

  /**
   * Uses the map of subscription IDs to phone IDs managed by {@link putPhoneId} and {@link
   * removePhoneId} to return the phone ID for a given subscription ID.
   */
  @Implementation(minSdk = LOLLIPOP_MR1, maxSdk = P)
  @HiddenApi
  protected static int getPhoneId(int subId) {
    if (phoneIds.containsKey(subId)) {
      return phoneIds.get(subId);
    }
    return INVALID_PHONE_INDEX;
  }

  @Resetter
  public static void reset() {
    defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    phoneIds.clear();
  }

  /** Builder class to create instance of {@link SubscriptionInfo}. */
  public static class SubscriptionInfoBuilder {
    private final SubscriptionInfo subscriptionInfo =
        ReflectionHelpers.callConstructor(SubscriptionInfo.class);

    public static SubscriptionInfoBuilder newBuilder() {
      return new SubscriptionInfoBuilder();
    }

    public SubscriptionInfo buildSubscriptionInfo() {
      return subscriptionInfo;
    }

    public SubscriptionInfoBuilder setId(int id) {
      ReflectionHelpers.setField(subscriptionInfo, "mId", id);
      return this;
    }

    public SubscriptionInfoBuilder setIccId(String iccId) {
      ReflectionHelpers.setField(subscriptionInfo, "mIccId", iccId);
      return this;
    }

    public SubscriptionInfoBuilder setSimSlotIndex(int index) {
      ReflectionHelpers.setField(subscriptionInfo, "mSimSlotIndex", index);
      return this;
    }

    public SubscriptionInfoBuilder setDisplayName(String name) {
      ReflectionHelpers.setField(subscriptionInfo, "mDisplayName", name);
      return this;
    }

    public SubscriptionInfoBuilder setCarrierName(String carrierName) {
      ReflectionHelpers.setField(subscriptionInfo, "mCarrierName", carrierName);
      return this;
    }

    public SubscriptionInfoBuilder setIconTint(int iconTint) {
      ReflectionHelpers.setField(subscriptionInfo, "mIconTint", iconTint);
      return this;
    }

    public SubscriptionInfoBuilder setNumber(String number) {
      ReflectionHelpers.setField(subscriptionInfo, "mNumber", number);
      return this;
    }

    public SubscriptionInfoBuilder setDataRoaming(int dataRoaming) {
      ReflectionHelpers.setField(subscriptionInfo, "mDataRoaming", dataRoaming);
      return this;
    }

    public SubscriptionInfoBuilder setCountryIso(String countryIso) {
      ReflectionHelpers.setField(subscriptionInfo, "mCountryIso", countryIso);
      return this;
    }

    public SubscriptionInfoBuilder setProfileClass(int profileClass) {
      ReflectionHelpers.setField(subscriptionInfo, "mProfileClass", profileClass);
      return this;
    }

    public SubscriptionInfoBuilder setIsEmbedded(boolean isEmbedded) {
      ReflectionHelpers.setField(subscriptionInfo, "mIsEmbedded", isEmbedded);
      return this;
    }

    public SubscriptionInfoBuilder setMnc(String mnc) {
      ReflectionHelpers.setField(subscriptionInfo, "mMnc", mnc);
      return this;
    }

    // Use {@link #newBuilder} to construct builders.
    private SubscriptionInfoBuilder() {}
  }
}