package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static org.robolectric.shadow.api.Shadow.directlyOn;

import android.Manifest.permission;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.IUserManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

/**
 * Robolectric implementation of {@link android.os.UserManager}.
 */
@Implements(value = UserManager.class, minSdk = JELLY_BEAN_MR1)
public class ShadowUserManager {

  /**
   * The default user ID user for secondary user testing, when the ID is not otherwise specified.
   */
  public static final int DEFAULT_SECONDARY_USER_ID = 10;

  public static final int FLAG_PRIMARY = UserInfo.FLAG_PRIMARY;
  public static final int FLAG_ADMIN = UserInfo.FLAG_ADMIN;
  public static final int FLAG_GUEST = UserInfo.FLAG_GUEST;
  public static final int FLAG_RESTRICTED = UserInfo.FLAG_RESTRICTED;

  private static boolean isMultiUserSupported = false;
  private static Map<Integer, Integer> userPidMap = new HashMap<>();

  @RealObject private UserManager realObject;

  private boolean userUnlocked = true;
  private boolean managedProfile = false;
  private boolean isSystemUser = true;
  private Map<Integer, Bundle> userRestrictions = new HashMap<>();
  private BiMap<UserHandle, Long> userProfiles = HashBiMap.create();
  private Map<String, Bundle> applicationRestrictions = new HashMap<>();
  private long nextUserSerial = 0;
  private Map<Integer, UserState> userState = new HashMap<>();
  private Map<Integer, UserInfo> userInfoMap = new HashMap<>();
  private Map<Integer, List<UserInfo>> profiles = new HashMap<>();
  private String seedAccountType;

  private Context context;
  private boolean enforcePermissions;
  private boolean canSwitchUser = false;

  @Implementation
  protected void __constructor__(Context context, IUserManager service) {
    this.context = context;
    addUser(UserHandle.USER_SYSTEM, "system_user", UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
  }

  /**
   * Compared to real Android, there is no check that the package name matches the application
   * package name and the method returns instantly.
   *
   * @see #setApplicationRestrictions(String, Bundle)
   */
  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected Bundle getApplicationRestrictions(String packageName) {
    Bundle bundle = applicationRestrictions.get(packageName);
    return bundle != null ? bundle : new Bundle();
  }

  /**
   * Sets the value returned by {@link UserManager#getApplicationRestrictions(String)}.
   */
  public void setApplicationRestrictions(String packageName, Bundle restrictions) {
    applicationRestrictions.put(packageName, restrictions);
  }

  /**
   * Adds a profile associated for the user that the calling process is running on.
   *
   * The user is assigned an arbitrary unique serial number.
   *
   * @return the user's serial number
   */
  public long addUserProfile(UserHandle userHandle) {
    long serialNumber = nextUserSerial++;
    userProfiles.put(userHandle, serialNumber);
    return serialNumber;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected List<UserHandle> getUserProfiles() {
    return ImmutableList.copyOf(userProfiles.keySet());
  }

  /**
   * If any profiles have been added using {@link #addProfile}, return those profiles.
   *
   * Otherwise follow real android behaviour.
   */
  @Implementation(minSdk = LOLLIPOP)
  protected List<UserInfo> getProfiles(int userHandle) {
    if (profiles.containsKey(userHandle)) {
      return ImmutableList.copyOf(profiles.get(userHandle));
    }
    return directlyOn(
        realObject, UserManager.class, "getProfiles", ClassParameter.from(int.class, userHandle));
  }

  /** Add a profile to be returned by {@link #getProfiles(int)}.**/
  public void addProfile(
      int userHandle, int profileUserHandle, String profileName, int profileFlags) {
    profiles.putIfAbsent(userHandle, new ArrayList<>());
    profiles.get(userHandle).add(new UserInfo(profileUserHandle, profileName, profileFlags));
  }

  @Implementation(minSdk = N)
  protected boolean isUserUnlocked() {
    return userUnlocked;
  }

  /**
   * Setter for {@link UserManager#isUserUnlocked()}
   */
  public void setUserUnlocked(boolean userUnlocked) {
    this.userUnlocked = userUnlocked;
  }

  /**
   * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application
   * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a
   * {@link SecurityManager} exception.
   *
   * @return `false` by default, or the value specified via {@link #setManagedProfile(boolean)}
   * @see #enforcePermissionChecks(boolean)
   * @see #setManagedProfile(boolean)
   */
  @Implementation(minSdk = LOLLIPOP)
  protected boolean isManagedProfile() {
    if (enforcePermissions && !hasManageUsersPermission()) {
      throw new SecurityException(
          "You need MANAGE_USERS permission to: check if specified user a " +
              "managed profile outside your profile group");
    }
    return managedProfile;
  }

  public void enforcePermissionChecks(boolean enforcePermissions) {
    this.enforcePermissions = enforcePermissions;
  }

  /**
   * Setter for {@link UserManager#isManagedProfile()}.
   */
  public void setManagedProfile(boolean managedProfile) {
    this.managedProfile = managedProfile;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
    Bundle bundle = userRestrictions.get(userHandle.getIdentifier());
    return bundle != null && bundle.getBoolean(restrictionKey);
  }

  public void setUserRestriction(UserHandle userHandle, String restrictionKey, boolean value) {
    Bundle bundle = getUserRestrictionsForUser(userHandle);
    bundle.putBoolean(restrictionKey, value);
  }

  /**
   * Removes all user restrictions set of a user identified by {@code userHandle}.
   */
  public void clearUserRestrictions(UserHandle userHandle) {
    userRestrictions.remove(userHandle.getIdentifier());
  }

  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected Bundle getUserRestrictions(UserHandle userHandle) {
    return new Bundle(getUserRestrictionsForUser(userHandle));
  }

  private Bundle getUserRestrictionsForUser(UserHandle userHandle) {
    Bundle bundle = userRestrictions.get(userHandle.getIdentifier());
    if (bundle == null) {
      bundle = new Bundle();
      userRestrictions.put(userHandle.getIdentifier(), bundle);
    }
    return bundle;
  }

  /**
   * @see #addUserProfile(UserHandle)
   */
  @Implementation
  protected long getSerialNumberForUser(UserHandle userHandle) {
    Long result = userProfiles.get(userHandle);
    return result == null ? -1L : result;
  }

  /**
   * @deprecated prefer {@link #addUserProfile(UserHandle)} to ensure consistency of profiles known
   * to the {@link UserManager}. Furthermore, calling this method for the current user, i.e: {@link
   * Process#myUserHandle()} is no longer necessary as this user is always known to UserManager and
   * has a preassigned serial number.
   */
  @Deprecated
  public void setSerialNumberForUser(UserHandle userHandle, long serialNumber) {
    userProfiles.put(userHandle, serialNumber);
  }

  /**
   * @see #addUserProfile(UserHandle)
   */
  @Implementation
  protected UserHandle getUserForSerialNumber(long serialNumber) {
    return userProfiles.inverse().get(serialNumber);
  }

  /** @see #addUserProfile(UserHandle) */
  @Implementation
  protected int getUserSerialNumber(@UserIdInt int userHandle) {
    for (Entry<UserHandle, Long> entry : userProfiles.entrySet()) {
      if (entry.getKey().getIdentifier() == userHandle) {
        return entry.getValue().intValue();
      }
    }
    return -1;
  }

  private boolean hasManageUsersPermission() {
    return context.getPackageManager().checkPermission(permission.MANAGE_USERS, context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
  }

  private void checkPermissions() {
    // TODO Ensure permisions
    //              throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS
    // permission "
    //                + "to: check " + name);throw new SecurityException();
  }

  /**
   * @return `false` by default, or the value specified via {@link #setIsDemoUser(boolean)}
   */
  @Implementation(minSdk = N_MR1)
  protected boolean isDemoUser() {
    return getUserInfo(UserHandle.myUserId()).isDemo();
  }

  /**
   * Sets that the current user is a demo user; controls the return value of {@link
   * UserManager#isDemoUser()}.
   *
   * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a demo user
   *     instead of changing default user flags.
   */
  @Deprecated
  public void setIsDemoUser(boolean isDemoUser) {
    UserInfo userInfo = getUserInfo(UserHandle.myUserId());
    if (isDemoUser) {
      userInfo.flags |= UserInfo.FLAG_DEMO;
    } else {
      userInfo.flags &= ~UserInfo.FLAG_DEMO;
    }
  }

  /**
   * @return 'true' by default, or the value specified via {@link #setIsSystemUser(boolean)}
   */
  @Implementation(minSdk = M)
  protected boolean isSystemUser() {
    if (isSystemUser == false) {
      return false;
    } else {
      return directlyOn(realObject, UserManager.class, "isSystemUser");
    }
  }

  /**
   * Sets that the current user is the system user; controls the return value of {@link
   * UserManager#isSystemUser()}.
   *
   * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a system user
   *     instead of changing default user flags.
   */
  @Deprecated
  public void setIsSystemUser(boolean isSystemUser) {
    this.isSystemUser = isSystemUser;
  }

  /**
   * Sets that the current user is the primary user; controls the return value of {@link
   * UserManager#isPrimaryUser()}.
   *
   * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a primary user
   *     instead of changing default user flags.
   */
  @Deprecated
  public void setIsPrimaryUser(boolean isPrimaryUser) {
    UserInfo userInfo = getUserInfo(UserHandle.myUserId());
    if (isPrimaryUser) {
      userInfo.flags |= UserInfo.FLAG_PRIMARY;
    } else {
      userInfo.flags &= ~UserInfo.FLAG_PRIMARY;
    }
  }

  /**
   * @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)}
   */
  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected boolean isLinkedUser() {
    return isRestrictedProfile();
  }

  /**
   * Sets that the current user is the linked user; controls the return value of {@link
   * UserManager#isLinkedUser()}.
   *
   * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a linked user
   *     instead of changing default user flags.
   */
  @Deprecated
  public void setIsLinkedUser(boolean isLinkedUser) {
    setIsRestrictedProfile(isLinkedUser);
  }

  /**
   * Returns 'false' by default, or the value specified via {@link
   * #setIsRestrictedProfile(boolean)}.
   */
  public boolean isRestrictedProfile() {
    return getUserInfo(UserHandle.myUserId()).isRestricted();
  }

  /**
   * Sets this process running under a restricted profile; controls the return value of {@link
   * UserManager#isRestrictedProfile()}.
   */
  public void setIsRestrictedProfile(boolean isRestrictedProfile) {
    UserInfo userInfo = getUserInfo(UserHandle.myUserId());
    if (isRestrictedProfile) {
      userInfo.flags |= UserInfo.FLAG_RESTRICTED;
    } else {
      userInfo.flags &= ~UserInfo.FLAG_RESTRICTED;
    }
  }

  /**
   * Sets that the current user is the guest user; controls the return value of {@link
   * UserManager#isGuestUser()}.
   *
   * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a guest user
   *     instead of changing default user flags.
   */
  @Deprecated
  public void setIsGuestUser(boolean isGuestUser) {
    UserInfo userInfo = getUserInfo(UserHandle.myUserId());
    if (isGuestUser) {
      userInfo.flags |= UserInfo.FLAG_GUEST;
    } else {
      userInfo.flags &= ~UserInfo.FLAG_GUEST;
    }
  }

  /**
   * @see #setUserState(UserHandle, UserState)
   */
  @Implementation
  protected boolean isUserRunning(UserHandle handle) {
    checkPermissions();
    UserState state = userState.get(handle.getIdentifier());

    if (state == UserState.STATE_RUNNING_LOCKED
        || state == UserState.STATE_RUNNING_UNLOCKED
        || state == UserState.STATE_RUNNING_UNLOCKING) {
      return true;
    } else {
      return false;
    }
  }

  /**
   * @see #setUserState(UserHandle, UserState)
   */
  @Implementation
  protected boolean isUserRunningOrStopping(UserHandle handle) {
    checkPermissions();
    UserState state = userState.get(handle.getIdentifier());

    if (state == UserState.STATE_RUNNING_LOCKED
        || state == UserState.STATE_RUNNING_UNLOCKED
        || state == UserState.STATE_RUNNING_UNLOCKING
        || state == UserState.STATE_STOPPING) {
      return true;
    } else {
      return false;
    }
  }

  /**
   * Describes the current state of the user. State can be set using
   * {@link #setUserState(UserHandle, UserState)}.
   */
  public enum UserState {
    // User is first coming up.
    STATE_BOOTING,
    // User is in the locked state.
    STATE_RUNNING_LOCKED,
    // User is in the unlocking state.
    STATE_RUNNING_UNLOCKING,
    // User is in the running state.
    STATE_RUNNING_UNLOCKED,
    // User is in the initial process of being stopped.
    STATE_STOPPING,
    // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN.
    STATE_SHUTDOWN
  }

  /**
   * Sets the current state for a given user, see {@link UserManager#isUserRunning(UserHandle)}
   * and {@link UserManager#isUserRunningOrStopping(UserHandle)}
   */
  public void setUserState(UserHandle handle, UserState state) {
    userState.put(handle.getIdentifier(), state);
  }

  @Implementation
  protected List<UserInfo> getUsers() {
    return new ArrayList<UserInfo>(userInfoMap.values());
  }

  @Implementation
  protected UserInfo getUserInfo(int userHandle) {
    return userInfoMap.get(userHandle);
  }

  /**
   * Returns {@code false} by default, or the value specified via {@link
   * #setCanSwitchUser(boolean)}.
   */
  @Implementation(minSdk = N)
  protected boolean canSwitchUsers() {
    return canSwitchUser;
  }

  /**
   * Sets whether switching users is allowed or not; controls the return value of {@link
   * UserManager#canSwitchUsers()}
   */
  public void setCanSwitchUser(boolean canSwitchUser) {
    this.canSwitchUser = canSwitchUser;
  }

  @Implementation(minSdk = Build.VERSION_CODES.Q)
  protected String getSeedAccountType() {
    return seedAccountType;
  }

  /** Setter for {@link UserManager#getSeedAccountType()} */
  public void setSeedAccountType(String seedAccountType) {
    this.seedAccountType = seedAccountType;
  }

  @Implementation(minSdk = JELLY_BEAN_MR1)
  protected boolean removeUser(int userHandle) {
    userInfoMap.remove(userHandle);
    return true;
  }

  @Implementation(minSdk = Q)
  protected boolean removeUser(UserHandle user) {
    return removeUser(user.getIdentifier());
  }

  @Implementation(minSdk = N)
  protected static boolean supportsMultipleUsers() {
    return isMultiUserSupported;
  }

  /**
   * Sets whether multiple users are supported; controls the return value of {@link
   * UserManager#supportsMultipleUser}.
   */
  public void setSupportsMultipleUsers(boolean isMultiUserSupported) {
    ShadowUserManager.isMultiUserSupported = isMultiUserSupported;
  }

  /**
   * Switches the current user to {@code userHandle}.
   *
   * @param userId the integer handle of the user, where 0 is the primary user.
   */
  public void switchUser(int userId) {
    if (!userInfoMap.containsKey(userId)) {
      throw new UnsupportedOperationException("Must add user before switching to it");
    }

    ShadowProcess.setUid(userPidMap.get(userId));
  }

  /**
   * Creates a user with the specified name, userId and flags.
   *
   * @param id the unique id of user
   * @param name name of the user
   * @param flags 16 bits for user type. See {@link UserInfo#flags}
   * @return a handle to the new user
   */
  public UserHandle addUser(int id, String name, int flags) {
    UserHandle userHandle =
        id == UserHandle.USER_SYSTEM ? Process.myUserHandle() : new UserHandle(id);
    addUserProfile(userHandle);
    setSerialNumberForUser(userHandle, (long) id);
    userInfoMap.put(id, new UserInfo(id, name, flags));
    userPidMap.put(
        id,
        id == UserHandle.USER_SYSTEM
            ? Process.myUid()
            : id * UserHandle.PER_USER_RANGE + ShadowProcess.getRandomApplicationUid());
    return userHandle;
  }

  @Resetter
  public static void reset() {
    if (userPidMap != null && userPidMap.isEmpty() == false) {
      ShadowProcess.setUid(userPidMap.get(UserHandle.USER_SYSTEM));

      userPidMap.clear();
      userPidMap.put(UserHandle.USER_SYSTEM, Process.myUid());
    }
  }
}


package org.robolectric.shadows;

import android.bluetooth.BluetoothSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

@Implements(BluetoothSocket.class)
public class ShadowBluetoothSocket {
  private final PipedOutputStream inputStreamFeeder = new PipedOutputStream();
  private final PipedInputStream outputStreamSink = new PipedInputStream();
  private final OutputStream outputStream;
  private final InputStream inputStream;

  private enum SocketState {
    INIT,
    CONNECTED,
    CLOSED,
  }

  private SocketState state = SocketState.INIT;

  public ShadowBluetoothSocket() {
    try {
      outputStream = new PipedOutputStream(outputStreamSink);
      inputStream = new PipedInputStream(inputStreamFeeder);
    } catch (IOException e) {
      // Shouldn't happen. Rethrow as an unchecked exception.
      throw new RuntimeException(e);
    }
  }

  /**
   * Returns {@link PipedOutputStream} that controls input stream of the {@link
   * BluetoothSocket}.
   */
  public PipedOutputStream getInputStreamFeeder() {
    return inputStreamFeeder;
  }

  /**
   * Returns {@link PipedInputStream} that controls output stream of the {@link
   * BluetoothSocket}.
   */
  public PipedInputStream getOutputStreamSink() {
    return outputStreamSink;
  }

  @Implementation
  protected InputStream getInputStream() {
    return inputStream;
  }

  @Implementation
  protected OutputStream getOutputStream() {
    return outputStream;
  }

  @Implementation
  protected boolean isConnected() {
    return state == SocketState.CONNECTED;
  }

  /** This method doesn't perform an actual connection and returns immediately */
  @Implementation
  protected void connect() throws IOException {
    if (state == SocketState.CLOSED) {
      throw new IOException("socket closed");
    }
    state = SocketState.CONNECTED;
  }

  @Implementation
  protected void close() throws IOException {
    state = SocketState.CLOSED;
  }
}


package org.robolectric.internal.bytecode;

import static java.lang.invoke.MethodType.methodType;

import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ListIterator;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

public class InvokeDynamicClassInstrumentor extends ClassInstrumentor {
  private static final Handle BOOTSTRAP_INIT;
  private static final Handle BOOTSTRAP;
  private static final Handle BOOTSTRAP_STATIC;
  private static final Handle BOOTSTRAP_INTRINSIC;

  static {
    String className = Type.getInternalName(InvokeDynamicSupport.class);

    MethodType bootstrap =
        methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    String bootstrapMethod =
        bootstrap.appendParameterTypes(MethodHandle.class).toMethodDescriptorString();
    String bootstrapIntrinsic =
        bootstrap.appendParameterTypes(String.class).toMethodDescriptorString();

    BOOTSTRAP_INIT = new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrapInit", bootstrap.toMethodDescriptorString());
    BOOTSTRAP = new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrap", bootstrapMethod);
    BOOTSTRAP_STATIC = new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrapStatic", bootstrapMethod);
    BOOTSTRAP_INTRINSIC = new Handle(Opcodes.H_INVOKESTATIC, className, "bootstrapIntrinsic", bootstrapIntrinsic);
  }

  public InvokeDynamicClassInstrumentor(Decorator decorator) {
    super(decorator);
  }

  @Override
  protected void addDirectCallConstructor(MutableClass mutableClass) {
    // not needed, for reasons.
  }

  @Override
  protected void writeCallToInitializing(MutableClass mutableClass,
      RobolectricGeneratorAdapter generator) {
    generator.invokeDynamic("initializing",
        Type.getMethodDescriptor(OBJECT_TYPE, mutableClass.classType), BOOTSTRAP_INIT);
  }

  @Override
  protected void generateClassHandlerCall(MutableClass mutableClass, MethodNode originalMethod,
      String originalMethodName, RobolectricGeneratorAdapter generator) {
    generateInvokeDynamic(mutableClass, originalMethod, originalMethodName, generator);
  }

  // todo javadocs
  private void generateInvokeDynamic(MutableClass mutableClass, MethodNode originalMethod,
      String originalMethodName, RobolectricGeneratorAdapter generator) {
    Handle original =
        new Handle(getTag(originalMethod), mutableClass.classType.getInternalName(),
            originalMethod.name, originalMethod.desc);

    if (generator.isStatic()) {
      generator.loadArgs();
      generator.invokeDynamic(originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original);
    } else {
      String desc = "(" + mutableClass.classType.getDescriptor() + originalMethod.desc.substring(1);
      generator.loadThis();
      generator.loadArgs();
      generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original);
    }

    generator.returnValue();
  }

  @Override
  protected void interceptInvokeVirtualMethod(MutableClass mutableClass,
      ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
    interceptInvokeVirtualMethodWithInvokeDynamic(instructions, targetMethod);
  }

  /**
   * Intercepts the method using the invokedynamic bytecode instruction available in Java 7+.
   * Should be called through interceptInvokeVirtualMethod, not directly.
   */
  private void interceptInvokeVirtualMethodWithInvokeDynamic(
      ListIterator<AbstractInsnNode> instructions, MethodInsnNode targetMethod) {
    instructions.remove();  // remove the method invocation

    Type type = Type.getObjectType(targetMethod.owner);
    String description = targetMethod.desc;
    String owner = type.getClassName();

    if (targetMethod.getOpcode() != Opcodes.INVOKESTATIC) {
      String thisType = type.getDescriptor();
      description = "(" + thisType + description.substring(1);
    }

    instructions.add(new InvokeDynamicInsnNode(targetMethod.name, description, BOOTSTRAP_INTRINSIC, owner));
  }
}


package org.robolectric.res.android;

// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h

import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Errors.UNKNOWN_ERROR;
import static org.robolectric.res.android.ResTable.APP_PACKAGE_ID;
import static org.robolectric.res.android.ResTable.Res_GETPACKAGE;
import static org.robolectric.res.android.ResTable.SYS_PACKAGE_ID;
import static org.robolectric.res.android.Util.ALOGW;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.robolectric.res.android.ResourceTypes.Res_value;

/**
 * Holds the shared library ID table. Shared libraries are assigned package IDs at
 * build time, but they may be loaded in a different order, so we need to maintain
 * a mapping of build-time package ID to run-time assigned package ID.
 *
 * Dynamic references are not currently supported in overlays. Only the base package
 * may have dynamic references.
 */
public class DynamicRefTable
{
  DynamicRefTable(byte packageId, boolean appAsLib) {
    this.mAssignedPackageId = packageId;
    this.mAppAsLib = appAsLib;

    mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
    mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
  }

//  // Loads an unmapped reference table from the package.
//  Errors load(final ResTable_lib_header header) {
//    return null;
//  }

  // Adds mappings from the other DynamicRefTable
  int addMappings(final DynamicRefTable other) {
    if (mAssignedPackageId != other.mAssignedPackageId) {
      return UNKNOWN_ERROR;
    }

//    final int entryCount = other.mEntries.size();
//    for (size_t i = 0; i < entryCount; i++) {
//      ssize_t index = mEntries.indexOfKey(other.mEntries.keyAt(i));
//      if (index < 0) {
//        mEntries.add(other.mEntries.keyAt(i), other.mEntries[i]);
//      } else {
//        if (other.mEntries[i] != mEntries[index]) {
//          return UNKNOWN_ERROR;
//        }
//      }
//    }
    for (Entry<String, Byte> otherEntry : other.mEntries.entrySet()) {
      String key = otherEntry.getKey();
      Byte curValue = mEntries.get(key);
      if (curValue == null) {
        mEntries.put(key, otherEntry.getValue());
      } else {
        if (!Objects.equals(otherEntry.getValue(), curValue)) {
          return UNKNOWN_ERROR;
        }
      }
    }

    // Merge the lookup table. No entry can conflict
    // (value of 0 means not set).
    for (int i = 0; i < 256; i++) {
      if (mLookupTable[i] != other.mLookupTable[i]) {
        if (mLookupTable[i] == 0) {
          mLookupTable[i] = other.mLookupTable[i];
        } else if (other.mLookupTable[i] != 0) {
          return UNKNOWN_ERROR;
        }
      }
    }
    return NO_ERROR;
  }

  // Creates a mapping from build-time package ID to run-time package ID for
  // the given package.
  int addMapping(final String packageName, byte packageId) {
    Byte index = mEntries.get(packageName);
    if (index == null) {
      return UNKNOWN_ERROR;
    }
    mLookupTable[index] = packageId;
    return NO_ERROR;
  }

//  // Performs the actual conversion of build-time resource ID to run-time
//  // resource ID.
  int lookupResourceId(Ref<Integer> resId) {
    int res = resId.get();
    int packageId = Res_GETPACKAGE(res) + 1;

    if (packageId == APP_PACKAGE_ID && !mAppAsLib) {
      // No lookup needs to be done, app package IDs are absolute.
      return NO_ERROR;
    }

    if (packageId == 0 || (packageId == APP_PACKAGE_ID && mAppAsLib)) {
      // The package ID is 0x00. That means that a shared library is accessing
      // its own local resource.
      // Or if app resource is loaded as shared library, the resource which has
      // app package Id is local resources.
      // so we fix up those resources with the calling package ID.
      resId.set((0xFFFFFF & (resId.get())) | (((int) mAssignedPackageId) << 24));
      return NO_ERROR;
    }

    // Do a proper lookup.
    int translatedId = mLookupTable[packageId];
    if (translatedId == 0) {
      ALOGW("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
          mAssignedPackageId, packageId);
      for (int i = 0; i < 256; i++) {
        if (mLookupTable[i] != 0) {
          ALOGW("e[0x%02x] . 0x%02x", i, mLookupTable[i]);
        }
      }
      return UNKNOWN_ERROR;
    }

    resId.set((res & 0x00ffffff) | (((int) translatedId) << 24));
    return NO_ERROR;
  }
//
  int lookupResourceValue(Ref<Res_value> value) {
    byte resolvedType = DataType.REFERENCE.code();
    Res_value inValue = value.get();
    switch (DataType.fromCode(inValue.dataType)) {
      case ATTRIBUTE:
        resolvedType = DataType.ATTRIBUTE.code();
        // fallthrough
      case REFERENCE:
        if (!mAppAsLib) {
          return NO_ERROR;
        }

        // If the package is loaded as shared library, the resource reference
        // also need to be fixed.
        break;
      case DYNAMIC_ATTRIBUTE:
        resolvedType = DataType.ATTRIBUTE.code();
        // fallthrough
      case DYNAMIC_REFERENCE:
        break;
      default:
        return NO_ERROR;
    }

    final Ref<Integer> resIdRef = new Ref<>(inValue.data);
    int err = lookupResourceId(resIdRef);
    value.set(inValue.withData(resIdRef.get()));
    if (err != NO_ERROR) {
      return err;
    }

    value.set(new Res_value(resolvedType, resIdRef.get()));
    return NO_ERROR;
 }

  public Map<String, Byte> entries() {
    return mEntries;
  }

  //
//  final KeyedVector<String16, uint8_t>& entries() final {
//  return mEntries;
//}
//
//  private:
    final byte                   mAssignedPackageId;
  final byte[]                         mLookupTable = new byte[256];
  final Map<String, Byte> mEntries = new HashMap<>();
  boolean                            mAppAsLib;
};


package org.robolectric.shadows;

import android.app.ActivityThread;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowActivity.IntentForResult;

@Implements(ContextWrapper.class)
public class ShadowContextWrapper {

  @RealObject
  private ContextWrapper realContextWrapper;

  public List<Intent> getBroadcastIntents() {
    return getShadowInstrumentation().getBroadcastIntents();
  }

  /**
   * Consumes the most recent {@code Intent} started by {@link
   * ContextWrapper#startActivity(android.content.Intent)} and returns it.
   *
   * @return the most recently started {@code Intent}
   */
  public Intent getNextStartedActivity() {
    return getShadowInstrumentation().getNextStartedActivity();
  }

  /**
   * Returns the most recent {@code Intent} started by {@link
   * ContextWrapper#startActivity(android.content.Intent)} without consuming it.
   *
   * @return the most recently started {@code Intent}
   */
  public Intent peekNextStartedActivity() {
    return getShadowInstrumentation().peekNextStartedActivity();
  }

  /**
   * Clears all {@code Intent}s started by {@link
   * ContextWrapper#startActivity(android.content.Intent)}.
   */
  public void clearNextStartedActivities() {
    getShadowInstrumentation().clearNextStartedActivities();
  }

  /**
   * Consumes the most recent {@code IntentForResult} started by {@link *
   * ContextWrapper#startActivity(android.content.Intent, android.os.Bundle)} and returns it.
   *
   * @return the most recently started {@code IntentForResult}
   */
  public IntentForResult getNextStartedActivityForResult() {
    return getShadowInstrumentation().getNextStartedActivityForResult();
  }

  /**
   * Returns the most recent {@code IntentForResult} started by {@link
   * ContextWrapper#startActivity(android.content.Intent, android.os.Bundle)} without consuming it.
   *
   * @return the most recently started {@code IntentForResult}
   */
  public IntentForResult peekNextStartedActivityForResult() {
    return getShadowInstrumentation().peekNextStartedActivityForResult();
  }

  /**
   * Consumes the most recent {@code Intent} started by {@link
   * android.content.Context#startService(android.content.Intent)} and returns it.
   *
   * @return the most recently started {@code Intent}
   */
  public Intent getNextStartedService() {
    return getShadowInstrumentation().getNextStartedService();
  }

  /**
   * Returns the most recent {@code Intent} started by
   * {@link android.content.Context#startService(android.content.Intent)} without consuming it.
   *
   * @return the most recently started {@code Intent}
   */
  public Intent peekNextStartedService() {
    return getShadowInstrumentation().peekNextStartedService();
  }

  /**
   * Clears all {@code Intent} started by
   * {@link android.content.Context#startService(android.content.Intent)}.
   */
  public void clearStartedServices() {
    getShadowInstrumentation().clearStartedServices();
  }

  /**
   * Consumes the {@code Intent} requested to stop a service by
   * {@link android.content.Context#stopService(android.content.Intent)}
   * from the bottom of the stack of stop requests.
   */
  public Intent getNextStoppedService() {
    return getShadowInstrumentation().getNextStoppedService();
  }

  /** Grant the given permissions for the current process and user. */
  public void grantPermissions(String... permissionNames) {
    getShadowInstrumentation().grantPermissions(permissionNames);
  }

  /** Grant the given permissions for the given process and user. */
  public void grantPermissions(int pid, int uid, String... permissions) {
    getShadowInstrumentation().grantPermissions(pid, uid, permissions);
  }

  /**
   * Revoke the given permissions for the current process and user.
   *
   * Has no effect if permissions were not previously granted.
   */
  public void denyPermissions(String... permissionNames) {
    getShadowInstrumentation().denyPermissions(permissionNames);
  }

  /** Revoke the given permissions for the given process and user. */
  public void denyPermissions(int pid, int uid, String... permissions) {
    getShadowInstrumentation().denyPermissions(pid, uid, permissions);
  }

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

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


package org.robolectric.plugins;

import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Priority;
import javax.inject.Inject;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.ConfigUtils;
import org.robolectric.pluginapi.Sdk;
import org.robolectric.pluginapi.SdkPicker;
import org.robolectric.pluginapi.UsesSdk;
import org.robolectric.pluginapi.config.ConfigurationStrategy.Configuration;

/** Robolectric's default {@link SdkPicker}. */
@SuppressWarnings("NewApi")
@AutoService(SdkPicker.class)
@Priority(Integer.MIN_VALUE)
public class DefaultSdkPicker implements SdkPicker {
  @Nonnull private final SdkCollection sdkCollection;

  private final Set<Sdk> enabledSdks;
  @Nonnull private final Sdk minKnownSdk;
  @Nonnull private final Sdk maxKnownSdk;

  @Inject
  public DefaultSdkPicker(@Nonnull SdkCollection sdkCollection, Properties systemProperties) {
    this(sdkCollection,
        systemProperties == null ? null : systemProperties.getProperty("robolectric.enabledSdks"));
  }

  @VisibleForTesting
  protected DefaultSdkPicker(@Nonnull SdkCollection sdkCollection, String enabledSdks) {
    this.sdkCollection = sdkCollection;
    this.enabledSdks = enumerateEnabledSdks(sdkCollection, enabledSdks);

    SortedSet<Sdk> sdks = this.sdkCollection.getKnownSdks();
    try {
      minKnownSdk = sdks.first();
      maxKnownSdk = sdks.last();
    } catch (NoSuchElementException e) {
      throw new RuntimeException("no SDKs are supported among " + sdkCollection.getKnownSdks(), e);
    }
  }

  /**
   * Enumerate the SDKs to be used for this test.
   *
   * @param configuration a collection of configuration objects, including {@link Config}
   * @param usesSdk the {@link UsesSdk} for the test
   * @return the list of candidate {@link Sdk}s.
   * @since 3.9
   */
  @Override
  @Nonnull
  public List<Sdk> selectSdks(Configuration configuration, UsesSdk usesSdk) {
    Config config = configuration.get(Config.class);
    Set<Sdk> sdks = new TreeSet<>(configuredSdks(config, usesSdk));
    if (enabledSdks != null) {
      sdks = Sets.intersection(sdks, enabledSdks);
    }
    return Lists.newArrayList(sdks);
  }

  @Nullable
  protected static Set<Sdk> enumerateEnabledSdks(
      SdkCollection sdkCollection, String enabledSdksString) {
    if (enabledSdksString == null || enabledSdksString.isEmpty()) {
      return null;
    } else {
      Set<Sdk> enabledSdks = new HashSet<>();
      for (int sdk : ConfigUtils.parseSdkArrayProperty(enabledSdksString)) {
        enabledSdks.add(sdkCollection.getSdk(sdk));
      }
      return enabledSdks;
    }
  }

  protected Set<Sdk> configuredSdks(Config config, UsesSdk usesSdk) {
    int appMinSdk = Math.max(usesSdk.getMinSdkVersion(), minKnownSdk.getApiLevel());
    int appTargetSdk = Math.max(usesSdk.getTargetSdkVersion(), minKnownSdk.getApiLevel());
    Integer appMaxSdk = usesSdk.getMaxSdkVersion();
    if (appMaxSdk == null) {
      appMaxSdk = maxKnownSdk.getApiLevel();
    }

    // For min/max SDK ranges...
    int minSdk = config.minSdk();
    int maxSdk = config.maxSdk();
    if (minSdk != -1 || maxSdk != -1) {
      int rangeMin = decodeSdk(minSdk, appMinSdk, appMinSdk, appTargetSdk, appMaxSdk);
      int rangeMax = decodeSdk(maxSdk, appMaxSdk, appMinSdk, appTargetSdk, appMaxSdk);

      if (rangeMin > rangeMax && (minSdk == -1 || maxSdk == -1)) {
        return Collections.emptySet();
      }

      return sdkRange(rangeMin, rangeMax);
    }

    // For explicitly-enumerated SDKs...
    if (config.sdk().length == 0) {
      if (appTargetSdk < appMinSdk) {
        throw new IllegalArgumentException(
            "Package targetSdkVersion=" + appTargetSdk + " < minSdkVersion=" + appMinSdk);
      } else if (appMaxSdk != 0 && appTargetSdk > appMaxSdk) {
        throw new IllegalArgumentException(
            "Package targetSdkVersion=" + appTargetSdk + " > maxSdkVersion=" + appMaxSdk);
      }
      return Collections.singleton(sdkCollection.getSdk(appTargetSdk));
    }

    if (config.sdk().length == 1 && config.sdk()[0] == Config.ALL_SDKS) {
      return sdkRange(appMinSdk, appMaxSdk);
    }

    Set<Sdk> sdks = new HashSet<>();
    for (int sdk : config.sdk()) {
      int decodedApiLevel = decodeSdk(sdk, appTargetSdk, appMinSdk, appTargetSdk, appMaxSdk);
      sdks.add(sdkCollection.getSdk(decodedApiLevel));
    }
    return sdks;
  }

  protected int decodeSdk(
      int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk) {
    if (value == Config.DEFAULT_VALUE_INT) {
      return defaultSdk;
    } else if (value == Config.NEWEST_SDK) {
      return appMaxSdk;
    } else if (value == Config.OLDEST_SDK) {
      return appMinSdk;
    } else if (value == Config.TARGET_SDK) {
      return appTargetSdk;
    } else {
      return value;
    }
  }

  @Nonnull
  protected Set<Sdk> sdkRange(int minSdk, int maxSdk) {
    if (maxSdk < minSdk) {
      throw new IllegalArgumentException("minSdk=" + minSdk + " is greater than maxSdk=" + maxSdk);
    }

    Set<Sdk> sdks = new HashSet<>();
    for (Sdk knownSdk : sdkCollection.getKnownSdks()) {
      int apiLevel = knownSdk.getApiLevel();
      if (apiLevel >= minSdk && knownSdk.getApiLevel() <= maxSdk) {
        sdks.add(knownSdk);
      }
    }

    if (sdks.isEmpty()) {
      throw new IllegalArgumentException(
          "No matching SDKs found for minSdk=" + minSdk + ", maxSdk=" + maxSdk);
    }

    return sdks;
  }

}


package org.robolectric.shadows.support.v4;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.widget.LinearLayout;
import org.robolectric.Robolectric;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.android.controller.ComponentController;

/**
 * FragmentController provides low-level APIs to control fragment's lifecycle.
 *
 * <p>Using FragmentController directly from your tests is strongly discouraged. You have to call
 * all the lifecycle callback methods (create, start, ...) in the same manner as the Android
 * framework by yourself otherwise you'll see fidelity issues. Consider using {@link
 * androidx.fragment.app.testing.FragmentScenario} instead, which provides higher-level, streamlined
 * APIs to control the lifecycle and it works with instrumentation tests too.
 *
 * @param <F> a class of the fragment which is under control by this class.
 */
public class SupportFragmentController<F extends Fragment>
    extends ComponentController<SupportFragmentController<F>, F> {
  private final F fragment;
  private final ActivityController<? extends FragmentActivity> activityController;

  protected SupportFragmentController(F fragment, Class<? extends FragmentActivity> activityClass) {
    this(fragment, activityClass, null);
  }

  protected SupportFragmentController(
      F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
    super(fragment, intent);
    this.fragment = fragment;
    this.activityController = Robolectric.buildActivity(activityClass, intent);
  }

  public static <F extends Fragment> SupportFragmentController<F> of(F fragment) {
    return new SupportFragmentController<>(fragment, FragmentControllerActivity.class);
  }

  public static <F extends Fragment> SupportFragmentController<F> of(
      F fragment, Class<? extends FragmentActivity> activityClass) {
    return new SupportFragmentController<>(fragment, activityClass);
  }

  public static <F extends Fragment> SupportFragmentController<F> of(
      F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
    return new SupportFragmentController<>(fragment, activityClass, intent);
  }

  /**
   * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
   * onResume() lifecycle methods, and then making it visible. Note that the fragment will be added
   * to the view with ID 1.
   */
  public static <F extends Fragment> F setupFragment(F fragment) {
    return SupportFragmentController.of(fragment).create().start().resume().visible().get();
  }

  /**
   * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
   * onResume() lifecycle methods, and then making it visible. Note that the fragment will be added
   * to the view with ID 1.
   */
  public static <F extends Fragment> F setupFragment(
      F fragment, Class<? extends FragmentActivity> fragmentActivityClass) {
    return SupportFragmentController.of(fragment, fragmentActivityClass)
        .create()
        .start()
        .resume()
        .visible()
        .get();
  }

  /**
   * Sets up the given fragment by attaching it to an activity created with the given bundle,
   * calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note
   * that the fragment will be added to the view with ID 1.
   */
  public static <F extends Fragment> F setupFragment(
      F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) {
    return SupportFragmentController.of(fragment, fragmentActivityClass)
        .create(bundle)
        .start()
        .resume()
        .visible()
        .get();
  }

  /**
   * Sets up the given fragment by attaching it to an activity created with the given bundle and
   * container id, calling its onCreate() through onResume() lifecycle methods, and then making it
   * visible.
   */
  public static <F extends Fragment> F setupFragment(
      F fragment,
      Class<? extends FragmentActivity> fragmentActivityClass,
      int containerViewId,
      Bundle bundle) {
    return SupportFragmentController.of(fragment, fragmentActivityClass)
        .create(containerViewId, bundle)
        .start()
        .postCreate(bundle)
        .resume()
        .visible()
        .get();
  }

  /**
   * Creates the activity with {@link Bundle} and adds the fragment to the view with ID
   * {@code contentViewId}.
   */
  public SupportFragmentController<F> create(final int contentViewId, final Bundle bundle) {
    shadowMainLooper.runPaused(
        new Runnable() {
          @Override
          public void run() {
            activityController
                .create(bundle)
                .get()
                .getSupportFragmentManager()
                .beginTransaction()
                .add(contentViewId, fragment)
                .commitNow();
          }
        });
    return this;
  }

  /**
   * Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment
   * will be added to the view with ID 1.
   */
  public SupportFragmentController<F> create(final Bundle bundle) {
    return create(1, bundle);
  }

  @Override
  public SupportFragmentController<F> create() {
    return create(null);
  }

  public SupportFragmentController<F> postCreate(Bundle bundle) {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.postCreate(bundle);
      }
    });
    return this;
  }

  @Override
  public SupportFragmentController<F> destroy() {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.destroy();
      }
    });
    return this;
  }

  public SupportFragmentController<F> start() {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.start();
      }
    });
    return this;
  }

  public SupportFragmentController<F> resume() {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.resume();
      }
    });
    return this;
  }

  public SupportFragmentController<F> pause() {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.pause();
      }
    });
    return this;
  }

  public SupportFragmentController<F> stop() {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.stop();
      }
    });
    return this;
  }

  public SupportFragmentController<F> visible() {
    shadowMainLooper.runPaused(new Runnable() {
      @Override
      public void run() {
        activityController.visible();
      }
    });
    return this;
  }

  public SupportFragmentController<F> saveInstanceState(final Bundle outState) {
    shadowMainLooper.runPaused(
        new Runnable() {
          @Override
          public void run() {
            activityController.saveInstanceState(outState);
          }
        });
    return this;
  }

  private static class FragmentControllerActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      LinearLayout view = new LinearLayout(this);
      view.setId(1);

      setContentView(view);
    }
  }
}