package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.Callback;
import android.content.pm.PackageParser.Package;
import android.content.pm.PackageUserState;
import android.os.Build;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import java.io.File;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implements;
import org.robolectric.res.Fs;
import org.robolectric.shadows.ShadowLog.LogItem;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
import org.robolectric.util.reflector.WithType;

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

  /** Parses an AndroidManifest.xml file using the framework PackageParser. */
  public static Package callParsePackage(Path apkFile) {
    PackageParser packageParser = new PackageParser();

    int flags = PackageParser.PARSE_IGNORE_PROCESSES;
    try {
      Package thePackage;
      if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.LOLLIPOP) {
        // TODO(christianw/brettchabot): workaround for NPE from probable bug in Q.
        // Can be removed when upstream properly handles a null callback
        // PackageParser#setMinAspectRatio(Package)
        if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
          QHelper.setCallback(packageParser);
        }
        thePackage = packageParser.parsePackage(apkFile.toFile(), flags);
      } else { // JB -> KK
        thePackage =
            reflector(_PackageParser_.class, packageParser)
                .parsePackage(
                    apkFile.toFile(), Fs.externalize(apkFile), new DisplayMetrics(), flags);
      }

      if (thePackage == null) {
        List<LogItem> logItems = ShadowLog.getLogsForTag("PackageParser");
        if (logItems.isEmpty()) {
          throw new RuntimeException(
              "Failed to parse package " + apkFile);
        } else {
          LogItem logItem = logItems.get(0);
          throw new RuntimeException(
              "Failed to parse package " + apkFile + ": " + logItem.msg, logItem.throwable);
        }
      }

      return thePackage;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Prevents ClassNotFoundError for Callback on pre-26.
   */
  private static class QHelper {
    private static void setCallback(PackageParser packageParser) {
      // TODO(christianw): this should be a CallbackImpl with the ApplicationPackageManager...
      packageParser.setCallback(new Callback() {
        @Override
        public boolean hasFeature(String s) {
          return false;
        }

        @Override
        public String[] getOverlayPaths(String s, String s1) {
          return null;
        }

        @Override
        public String[] getOverlayApks(String s) {
          return null;
        }
      });
    }
  }

  /** Accessor interface for {@link PackageParser}'s internals. */
  @ForType(PackageParser.class)
  interface _PackageParser_ {

    // <= JELLY_BEAN
    @Static
    PackageInfo generatePackageInfo(
        PackageParser.Package p,
        int[] gids,
        int flags,
        long firstInstallTime,
        long lastUpdateTime,
        HashSet<String> grantedPermissions);

    // <= LOLLIPOP
    @Static
    PackageInfo generatePackageInfo(
        PackageParser.Package p,
        int[] gids,
        int flags,
        long firstInstallTime,
        long lastUpdateTime,
        HashSet<String> grantedPermissions,
        @WithType("android.content.pm.PackageUserState")
            Object state);

    // LOLLIPOP_MR1
    @Static
    PackageInfo generatePackageInfo(
        PackageParser.Package p,
        int[] gids,
        int flags,
        long firstInstallTime,
        long lastUpdateTime,
        ArraySet<String> grantedPermissions,
        @WithType("android.content.pm.PackageUserState")
            Object state);

    default PackageInfo generatePackageInfo(
        PackageParser.Package p,
        int[] gids,
        int flags,
        long firstInstallTime,
        long lastUpdateTime) {
      int apiLevel = RuntimeEnvironment.getApiLevel();

      if (apiLevel <= JELLY_BEAN) {
        return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
            new HashSet<>());
      } else if (apiLevel <= LOLLIPOP) {
        return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
            new HashSet<>(), new PackageUserState());
      } else if (apiLevel <= LOLLIPOP_MR1) {
        return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
            new ArraySet<>(), new PackageUserState());
      } else {
        return PackageParser.generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
            new HashSet<>(), new PackageUserState());
      }
    }

    Package parsePackage(File file, String fileName, DisplayMetrics displayMetrics, int flags);
  }

  /** Accessor interface for {@link Package}'s internals. */
  @ForType(Package.class)
  public interface _Package_ {

    @Accessor("mPath")
    String getPath();
  }
}


package org.robolectric.android.internal;

import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import android.app.Activity;
import android.app.Instrumentation.ActivityResult;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import androidx.test.internal.platform.app.ActivityInvoker;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import javax.annotation.Nullable;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.android.fakes.RoboMonitoringInstrumentation;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowActivity;

/**
 * An {@link ActivityInvoker} that drives {@link Activity} lifecycles manually.
 *
 * <p>All the methods in this class are blocking API.
 */
public class LocalActivityInvoker implements ActivityInvoker {

  @Nullable private ActivityController<? extends Activity> controller;

  @Override
  public void startActivity(Intent intent) {
    controller = getInstrumentation().startActivitySyncInternal(intent);
  }

  @Override
  public ActivityResult getActivityResult() {
    checkNotNull(controller);
    checkState(controller.get().isFinishing(), "You must finish your Activity first");
    ShadowActivity shadowActivity = Shadow.extract(controller.get());
    return new ActivityResult(shadowActivity.getResultCode(), shadowActivity.getResultIntent());
  }

  @Override
  public void resumeActivity(Activity activity) {
    checkNotNull(controller);
    checkState(controller.get() == activity);
    Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
    switch (stage) {
      case RESUMED:
        return;
      case PAUSED:
        controller.stop().restart().start().resume();
        return;
      case STOPPED:
        controller.restart().start().resume();
        return;
      default:
        throw new IllegalStateException(
            String.format(
                "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
    }
  }

  @Override
  public void pauseActivity(Activity activity) {
    checkNotNull(controller);
    checkState(controller.get() == activity);
    Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
    switch (stage) {
      case RESUMED:
        controller.pause();
        return;
      case PAUSED:
        return;
      default:
        throw new IllegalStateException(
            String.format("Activity's stage must be RESUMED or PAUSED but was %s.", stage));
    }
  }

  @Override
  public void stopActivity(Activity activity) {
    checkNotNull(controller);
    checkState(controller.get() == activity);
    Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
    switch (stage) {
      case RESUMED:
        controller.pause().stop();
        return;
      case PAUSED:
        controller.stop();
        return;
      case STOPPED:
        return;
      default:
        throw new IllegalStateException(
            String.format(
                "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
    }
  }

  @Override
  public void recreateActivity(Activity activity) {
    checkNotNull(controller);
    checkState(controller.get() == activity);
    controller.recreate();
  }

  @Override
  public void finishActivity(Activity activity) {
    checkNotNull(controller);
    checkState(controller.get() == activity);
    activity.finish();
    Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
    switch (stage) {
      case RESUMED:
        controller.pause().stop().destroy();
        return;
      case PAUSED:
        controller.stop().destroy();
        return;
      case STOPPED:
        controller.destroy();
        return;
      default:
        throw new IllegalStateException(
            String.format(
                "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
    }
  }

  // This implementation makes sure, that the activity you are trying to launch exists
  @Override
  public Intent getIntentForActivity(Class<? extends Activity> activityClass) {
    PackageManager packageManager = getTargetContext().getPackageManager();
    ComponentName componentName = new ComponentName(getTargetContext(), activityClass);
    Intent intent = Intent.makeMainActivity(componentName);
    if (packageManager.resolveActivity(intent, 0) != null) {
      return intent;
    }
    return Intent.makeMainActivity(new ComponentName(getContext(), activityClass));
  }

  private static RoboMonitoringInstrumentation getInstrumentation() {
    return (RoboMonitoringInstrumentation) InstrumentationRegistry.getInstrumentation();
  }
}


package org.robolectric.res.android;

import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Errors.NO_INIT;
import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE;
import static org.robolectric.res.android.ResourceTypes.RES_TABLE_LIBRARY_TYPE;
import static org.robolectric.res.android.ResourceTypes.RES_TABLE_PACKAGE_TYPE;
import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE;
import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_SPEC_TYPE;
import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_TYPE;
import static org.robolectric.res.android.ResourceTypes.kResTableTypeMinSize;
import static org.robolectric.res.android.ResourceUtils.make_resid;
import static org.robolectric.res.android.Util.UNLIKELY;
import static org.robolectric.res.android.Util.dtohl;
import static org.robolectric.res.android.Util.dtohs;
import static org.robolectric.res.android.Util.isTruthy;
import static org.robolectric.res.android.Util.logError;
import static org.robolectric.res.android.Util.logWarning;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.robolectric.res.android.Chunk.Iterator;
import org.robolectric.res.android.Idmap.LoadedIdmap;
import org.robolectric.res.android.ResourceTypes.IdmapEntry_header;
import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
import org.robolectric.res.android.ResourceTypes.ResTable_entry;
import org.robolectric.res.android.ResourceTypes.ResTable_header;
import org.robolectric.res.android.ResourceTypes.ResTable_lib_entry;
import org.robolectric.res.android.ResourceTypes.ResTable_lib_header;
import org.robolectric.res.android.ResourceTypes.ResTable_map;
import org.robolectric.res.android.ResourceTypes.ResTable_map_entry;
import org.robolectric.res.android.ResourceTypes.ResTable_package;
import org.robolectric.res.android.ResourceTypes.ResTable_sparseTypeEntry;
import org.robolectric.res.android.ResourceTypes.ResTable_type;
import org.robolectric.res.android.ResourceTypes.ResTable_typeSpec;
import org.robolectric.res.android.ResourceTypes.Res_value;

// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/LoadedArsc.h
// and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/LoadedArsc.cpp
public class LoadedArsc {

  //#ifndef LOADEDARSC_H_
//#define LOADEDARSC_H_
//
//#include <memory>
//#include <set>
//#include <vector>
//
//#include "android-base/macros.h"
//
//#include "androidfw/ByteBucketArray.h"
//#include "androidfw/Chunk.h"
//#include "androidfw/ResourceTypes.h"
//#include "androidfw/Util.h"
//
//namespace android {
//
  static class DynamicPackageEntry {

    // public:
    //
    // DynamicPackageEntry() =default;

    DynamicPackageEntry(String package_name, int package_id) {
      this.package_name = package_name;
      this.package_id = package_id;
    }

    String package_name;
    int package_id = 0;
  }

  // TypeSpec is going to be immediately proceeded by
// an array of Type structs, all in the same block of memory.
  static class TypeSpec {

    public static final int SIZEOF = ResTable_typeSpec.SIZEOF + IdmapEntry_header.SIZEOF;

    // Pointer to the mmapped data where flags are kept.
    // Flags denote whether the resource entry is public
    // and under which configurations it varies.
    ResTable_typeSpec type_spec;

    // Pointer to the mmapped data where the IDMAP mappings for this type
    // exist. May be nullptr if no IDMAP exists.
    IdmapEntry_header idmap_entries;

    // The number of types that follow this struct.
    // There is a type for each configuration that entries are defined for.
    int type_count;

    // Trick to easily access a variable number of Type structs
    // proceeding this struct, and to ensure their alignment.
    // ResTable_type* types[0];
    ResTable_type[] types;

    int GetFlagsForEntryIndex(int entry_index) {
      if (entry_index >= dtohl(type_spec.entryCount)) {
        return 0;
      }

      // uint32_t* flags = reinterpret_cast<uint32_t*>(type_spec + 1);
      int[] flags = type_spec.getSpecFlags();
      return flags[entry_index];
    }
  }

  // Returns the string pool where all string resource values
  // (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
  public ResStringPool GetStringPool() {
    return global_string_pool_;
  }

  // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
  List<LoadedPackage> GetPackages() {
    return packages_;
  }

  // Returns true if this is a system provided resource.
  boolean IsSystem() {
    return system_;
  }

  //
// private:
//  DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
//
//  LoadedArsc() = default;
//   bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
//
  final ResStringPool global_string_pool_ = new ResStringPool();
  final List<LoadedPackage> packages_ = new ArrayList<>();
  boolean system_ = false;
//};
//
//}  // namespace android
//
//#endif /* LOADEDARSC_H_ */

//  #define ATRACE_TAG ATRACE_TAG_RESOURCES
//
//  #include "androidfw/LoadedArsc.h"
//
//  #include <cstddef>
//  #include <limits>
//
//  #include "android-base/logging.h"
//  #include "android-base/stringprintf.h"
//  #include "utils/ByteOrder.h"
//  #include "utils/Trace.h"
//
//  #ifdef _WIN32
//  #ifdef ERROR
//  #undef ERROR
//  #endif
//  #endif
//
//  #include "androidfw/ByteBucketArray.h"
//  #include "androidfw/Chunk.h"
//  #include "androidfw/ResourceUtils.h"
//  #include "androidfw/Util.h"
//
//  using android::base::StringPrintf;
//
//  namespace android {

  static final int kAppPackageId = 0x7f;

//  namespace {

  // Builder that helps accumulate Type structs and then create a single
  // contiguous block of memory to store both the TypeSpec struct and
  // the Type structs.
  static class TypeSpecPtrBuilder {
    // public:
    TypeSpecPtrBuilder(ResTable_typeSpec header, IdmapEntry_header idmap_header) {
      this.header_ = header;
      this.idmap_header_ = idmap_header;
    }

    void AddType(ResTable_type type) {
      types_.add(type);
    }

    TypeSpec Build() {
      // Check for overflow.
      // using ElementType = ResTable_type*;
      // if ((std.numeric_limits<size_t>.max() - sizeof(TypeSpec)) / sizeof(ElementType) <
      //     types_.size()) {
      if ((Integer.MAX_VALUE - TypeSpec.SIZEOF) / 4 < types_.size()) {
        return null; // {} ;
      }
      // TypeSpec* type_spec =
      //     (TypeSpec*).malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
      TypeSpec type_spec = new TypeSpec();
      type_spec.types = new ResTable_type[types_.size()];
      type_spec.type_spec = header_;
      type_spec.idmap_entries = idmap_header_;
      type_spec.type_count = types_.size();
      // memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
      for (int i = 0; i < type_spec.types.length; i++) {
        type_spec.types[i] = types_.get(i);

      }
      return type_spec;
    }

    // private:
    // DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);

    ResTable_typeSpec header_;
    IdmapEntry_header idmap_header_;
    final List<ResTable_type> types_ = new ArrayList<>();
  };

//  }  // namespace

  // Precondition: The header passed in has already been verified, so reading any fields and trusting
// the ResChunk_header is safe.
  static boolean VerifyResTableType(ResTable_type header) {
    if (header.id == 0) {
      logError("RES_TABLE_TYPE_TYPE has invalid ID 0.");
      return false;
    }

    int entry_count = dtohl(header.entryCount);
    // if (entry_count > std.numeric_limits<uint16_t>.max()) {
    if (entry_count > 0xffff) {
      logError("RES_TABLE_TYPE_TYPE has too many entries (" + entry_count + ").");
      return false;
    }

    // Make sure that there is enough room for the entry offsets.
    int offsets_offset = dtohs(header.header.headerSize);
    int entries_offset = dtohl(header.entriesStart);
    int offsets_length = 4 * entry_count;

    if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
      logError("RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.");
      return false;
    }

    if (entries_offset > dtohl(header.header.size)) {
      logError("RES_TABLE_TYPE_TYPE entry offsets extend beyond chunk.");
      return false;
    }

    if (isTruthy(entries_offset & 0x03)) {
      logError("RES_TABLE_TYPE_TYPE entries start at unaligned address.");
      return false;
    }
    return true;
  }

  static boolean VerifyResTableEntry(ResTable_type type, int entry_offset) {
    // Check that the offset is aligned.
    if (isTruthy(entry_offset & 0x03)) {
      logError("Entry at offset " + entry_offset + " is not 4-byte aligned.");
      return false;
    }

    // Check that the offset doesn't overflow.
    // if (entry_offset > std.numeric_limits<int>.max() - dtohl(type.entriesStart)) {
    if (entry_offset > Integer.MAX_VALUE - dtohl(type.entriesStart)) {
      // Overflow in offset.
      logError("Entry at offset " + entry_offset + " is too large.");
      return false;
    }

    int chunk_size = dtohl(type.header.size);

    entry_offset += dtohl(type.entriesStart);
    if (entry_offset > chunk_size - ResTable_entry.SIZEOF) {
      logError("Entry at offset " + entry_offset
          + " is too large. No room for ResTable_entry.");
      return false;
    }

    // ResTable_entry* entry = reinterpret_cast<ResTable_entry*>(
    //       reinterpret_cast<uint8_t*>(type) + entry_offset);
    ResTable_entry entry = new ResTable_entry(type.myBuf(), type.myOffset() + entry_offset);

    int entry_size = dtohs(entry.size);
    // if (entry_size < sizeof(*entry)) {
    if (entry_size < ResTable_entry.SIZEOF) {
      logError("ResTable_entry size " + entry_size + " at offset " + entry_offset
          + " is too small.");
      return false;
    }

    if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
      logError("ResTable_entry size " + entry_size + " at offset " + entry_offset
          + " is too large.");
      return false;
    }

    if (entry_size < ResTable_map_entry.BASE_SIZEOF) {
      // There needs to be room for one Res_value struct.
      if (entry_offset + entry_size > chunk_size - Res_value.SIZEOF) {
        logError("No room for Res_value after ResTable_entry at offset " + entry_offset
            + " for type " + (int) type.id + ".");
        return false;
      }

      // Res_value value =
      //       reinterpret_cast<Res_value*>(reinterpret_cast<uint8_t*>(entry) + entry_size);
      Res_value value =
          new Res_value(entry.myBuf(), entry.myOffset() + ResTable_entry.SIZEOF);
      int value_size = dtohs(value.size);
      if (value_size < Res_value.SIZEOF) {
        logError("Res_value at offset " + entry_offset + " is too small.");
        return false;
      }

      if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
        logError("Res_value size " + value_size + " at offset " + entry_offset
            + " is too large.");
        return false;
      }
    } else {
      ResTable_map_entry map = new ResTable_map_entry(entry.myBuf(), entry.myOffset());
      int map_entry_count = dtohl(map.count);
      int map_entries_start = entry_offset + entry_size;
      if (isTruthy(map_entries_start & 0x03)) {
        logError("Map entries at offset " + entry_offset + " start at unaligned offset.");
        return false;
      }

      // Each entry is sizeof(ResTable_map) big.
      if (map_entry_count > ((chunk_size - map_entries_start) / ResTable_map.SIZEOF)) {
        logError("Too many map entries in ResTable_map_entry at offset " + entry_offset + ".");
        return false;
      }
    }
    return true;
  }

  static class LoadedPackage {
    // private:

    // DISALLOW_COPY_AND_ASSIGN(LoadedPackage);

    // LoadedPackage();

    ResStringPool type_string_pool_ = new ResStringPool();
    ResStringPool key_string_pool_ = new ResStringPool();
    String package_name_;
    int package_id_ = -1;
    int type_id_offset_ = 0;
    boolean dynamic_ = false;
    boolean system_ = false;
    boolean overlay_ = false;

    // final ByteBucketArray<TypeSpec> type_specs_ = new ByteBucketArray<TypeSpec>() {
    //   @Override
    //   TypeSpec newInstance() {
    //     return new TypeSpec();
    //   }
    // };
    final Map<Integer, TypeSpec> type_specs_ = new HashMap<>();
    final List<DynamicPackageEntry> dynamic_package_map_ = new ArrayList<>();

    ResTable_entry GetEntry(ResTable_type type_chunk,
        short entry_index) {
      int entry_offset = GetEntryOffset(type_chunk, entry_index);
      if (entry_offset == ResTable_type.NO_ENTRY) {
        return null;
      }
      return GetEntryFromOffset(type_chunk, entry_offset);
    }

    static int GetEntryOffset(ResTable_type type_chunk, int entry_index) {
      // The configuration matches and is better than the previous selection.
      // Find the entry value if it exists for this configuration.
      int entry_count = dtohl(type_chunk.entryCount);
      int offsets_offset = dtohs(type_chunk.header.headerSize);

      // Check if there is the desired entry in this type.

      if (isTruthy(type_chunk.flags & ResTable_type.FLAG_SPARSE)) {
        // This is encoded as a sparse map, so perform a binary search.
        // ResTable_sparseTypeEntry sparse_indices =
        //     reinterpret_cast<ResTable_sparseTypeEntry*>(
        //         reinterpret_cast<uint8_t*>(type_chunk) + offsets_offset);
        // ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
        // ResTable_sparseTypeEntry* result =
        //     std.lower_bound(sparse_indices, sparse_indices_end, entry_index,
        //         [](ResTable_sparseTypeEntry& entry, short entry_idx) {
        //   return dtohs(entry.idx) < entry_idx;
        // });
        ResTable_sparseTypeEntry result = null;
        for (int i = 0; i < entry_count; i++) {
          ResTable_sparseTypeEntry entry = new ResTable_sparseTypeEntry(type_chunk.myBuf(),
              type_chunk.myOffset() + offsets_offset);
          if (entry.idxOrOffset >= entry_index) {
            result = entry;
            break;
          }
        }

        if (result == null || dtohs(result.idxOrOffset) != entry_index) {
          // No entry found.
          return ResTable_type.NO_ENTRY;
        }

        // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
        // the real offset divided by 4.
        // return int{dtohs(result.offset)} * 4u;
        return dtohs(result.idxOrOffset) * 4;
      }

      // This type is encoded as a dense array.
      if (entry_index >= entry_count) {
        // This entry cannot be here.
        return ResTable_type.NO_ENTRY;
      }

      // int* entry_offsets = reinterpret_cast<int*>(
      //     reinterpret_cast<uint8_t*>(type_chunk) + offsets_offset);
      // return dtohl(entry_offsets[entry_index]);
      return dtohl(type_chunk.entryOffset(entry_index));
    }

    static ResTable_entry GetEntryFromOffset(ResTable_type type_chunk,
        int offset) {
      if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) {
        return null;
      }
      // return reinterpret_cast<ResTable_entry*>(reinterpret_cast<uint8_t*>(type_chunk) +
      //     offset + dtohl(type_chunk.entriesStart));
      return new ResTable_entry(type_chunk.myBuf(),
          type_chunk.myOffset() + offset + dtohl(type_chunk.entriesStart));
    }

    void CollectConfigurations(boolean exclude_mipmap,
        Set<ResTable_config> out_configs) {
      String kMipMap = "mipmap";
      int type_count = type_specs_.size();
      for (int i = 0; i < type_count; i++) {
        TypeSpec type_spec = type_specs_.get(i);
        if (type_spec != null) {
          if (exclude_mipmap) {
            int type_idx = type_spec.type_spec.id - 1;
            final Ref<Integer> type_name_len = new Ref<>(0);
            String type_name16 = type_string_pool_.stringAt(type_idx, type_name_len);
            if (type_name16 != null) {
              // if (kMipMap.compare(0, std::u16string::npos,type_name16, type_name_len) ==0){
              if (kMipMap.equals(type_name16)) {
                // This is a mipmap type, skip collection.
                continue;
              }
            }
            String type_name = type_string_pool_.string8At(type_idx, type_name_len);
            if (type_name != null) {
              // if (strncmp(type_name, "mipmap", type_name_len) == 0) {
              if ("mipmap".equals(type_name))
                // This is a mipmap type, skip collection.
                continue;
            }
          }
        }

        for (ResTable_type iter : type_spec.types) {
          ResTable_config config = ResTable_config.fromDtoH(iter.config);
          out_configs.add(config);
        }
      }
    }

    void CollectLocales(boolean canonicalize, Set<String> out_locales) {
      // char temp_locale[ RESTABLE_MAX_LOCALE_LEN];
      String temp_locale;
      int type_count = type_specs_.size();
      for (int i = 0; i < type_count; i++) {
        TypeSpec type_spec = type_specs_.get(i);
        if (type_spec != null) {
          for (ResTable_type iter : type_spec.types) {
            ResTable_config configuration = ResTable_config.fromDtoH(iter.config);
            if (configuration.locale() != 0) {
              temp_locale = configuration.getBcp47Locale(canonicalize);
              String locale = temp_locale;
              out_locales.add(locale);
            }
          }
        }
      }
    }

    // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
    // the underlying ResStringPool API expects this. For now this is acceptable, but since
    // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
    // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
    // for patching the correct package ID to the resource ID.
    int FindEntryByName(String type_name, String entry_name) {
      int type_idx = type_string_pool_.indexOfString(type_name);
      if (type_idx < 0) {
        return 0;
      }

      int key_idx = key_string_pool_.indexOfString(entry_name);
      if (key_idx < 0) {
        return 0;
      }

      TypeSpec type_spec = type_specs_.get(type_idx);
      if (type_spec == null) {
        return 0;
      }

      for (ResTable_type iter : type_spec.types) {
        ResTable_type type = iter;
        int entry_count = type.entryCount;

        for (int entry_idx = 0; entry_idx < entry_count; entry_idx++) {
          // const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
          //     reinterpret_cast<const uint8_t*>(type.type) + dtohs(type.type.header.headerSize));
          // ResTable_type entry_offsets = new ResTable_type(type.myBuf(),
          //     type.myOffset() + type.header.headerSize);
          // int offset = dtohl(entry_offsets[entry_idx]);
          int offset = dtohl(type.entryOffset(entry_idx));
          if (offset != ResTable_type.NO_ENTRY) {
            // const ResTable_entry* entry =
            //     reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type.type) +
            //     dtohl(type.type.entriesStart) + offset);
            ResTable_entry entry =
                new ResTable_entry(type.myBuf(), type.myOffset() +
                    dtohl(type.entriesStart) + offset);
            if (dtohl(entry.key.index) == key_idx) {
              // The package ID will be overridden by the caller (due to runtime assignment of package
              // IDs for shared libraries).
              return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), (short) entry_idx);
            }
          }
        }
      }
      return 0;
    }

    static LoadedPackage Load(Chunk chunk,
        LoadedIdmap loaded_idmap,
        boolean system, boolean load_as_shared_library) {
      // ATRACE_NAME("LoadedPackage::Load");
      LoadedPackage loaded_package = new LoadedPackage();

      // typeIdOffset was added at some point, but we still must recognize apps built before this
      // was added.
      // constexpr int kMinPackageSize =
      //     sizeof(ResTable_package) - sizeof(ResTable_package.typeIdOffset);
      final int kMinPackageSize = ResTable_package.SIZEOF - 4;
      // ResTable_package header = chunk.header<ResTable_package, kMinPackageSize>();
      ResTable_package header = chunk.asResTable_package(kMinPackageSize);
      if (header == null) {
        logError("RES_TABLE_PACKAGE_TYPE too small.");
        return emptyBraces();
      }

      loaded_package.system_ = system;

      loaded_package.package_id_ = dtohl(header.id);
      if (loaded_package.package_id_ == 0 ||
          (loaded_package.package_id_ == kAppPackageId && load_as_shared_library)) {
        // Package ID of 0 means this is a shared library.
        loaded_package.dynamic_ = true;
      }

      if (loaded_idmap != null) {
        // This is an overlay and so it needs to pretend to be the target package.
        loaded_package.package_id_ = loaded_idmap.TargetPackageId();
        loaded_package.overlay_ = true;
      }

      if (header.header.headerSize >= ResTable_package.SIZEOF) {
        int type_id_offset = dtohl(header.typeIdOffset);
        // if (type_id_offset > std.numeric_limits<uint8_t>.max()) {
        if (type_id_offset > 255) {
          logError("RES_TABLE_PACKAGE_TYPE type ID offset too large.");
          return emptyBraces();
        }
        loaded_package.type_id_offset_ = type_id_offset;
      }

      loaded_package.package_name_ = Util
          .ReadUtf16StringFromDevice(header.name, header.name.length);

      // A map of TypeSpec builders, each associated with an type index.
      // We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
      // contiguous block of memory that holds all the Types together with the TypeSpec.
      Map<Integer, TypeSpecPtrBuilder> type_builder_map = new HashMap<>();

      Chunk.Iterator iter = new Iterator(chunk.data_ptr(), chunk.data_size());
      while (iter.HasNext()) {
        Chunk child_chunk = iter.Next();
        switch (child_chunk.type()) {
          case RES_STRING_POOL_TYPE: {
            // uintptr_t pool_address =
            //     reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
            // uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
            int pool_address =
                child_chunk.myOffset();
            int header_address = header.myOffset();
            if (pool_address == header_address + dtohl(header.typeStrings)) {
              // This string pool is the type string pool.
              int err = loaded_package.type_string_pool_.setTo(
                  child_chunk.myBuf(), child_chunk.myOffset(), child_chunk.size(), false);
              if (err != NO_ERROR) {
                logError("RES_STRING_POOL_TYPE for types corrupt.");
                return emptyBraces();
              }
            } else if (pool_address == header_address + dtohl(header.keyStrings)) {
              // This string pool is the key string pool.
              int err = loaded_package.key_string_pool_.setTo(
                  child_chunk.myBuf(), child_chunk.myOffset(), child_chunk.size(), false);
              if (err != NO_ERROR) {
                logError("RES_STRING_POOL_TYPE for keys corrupt.");
                return emptyBraces();
              }
            } else {
              logWarning("Too many RES_STRING_POOL_TYPEs found in RES_TABLE_PACKAGE_TYPE.");
            }
          } break;

          case RES_TABLE_TYPE_SPEC_TYPE: {
            ResTable_typeSpec type_spec = new ResTable_typeSpec(child_chunk.myBuf(),
                child_chunk.myOffset());
            if (type_spec == null) {
              logError("RES_TABLE_TYPE_SPEC_TYPE too small.");
              return emptyBraces();
            }

            if (type_spec.id == 0) {
              logError("RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0.");
              return emptyBraces();
            }

            // if (loaded_package.type_id_offset_ + static_cast<int>(type_spec.id) >
            //     std.numeric_limits<uint8_t>.max()) {
            if (loaded_package.type_id_offset_ + type_spec.id > 255) {
              logError("RES_TABLE_TYPE_SPEC_TYPE has out of range ID.");
              return emptyBraces();
            }

            // The data portion of this chunk contains entry_count 32bit entries,
            // each one representing a set of flags.
            // Here we only validate that the chunk is well formed.
            int entry_count = dtohl(type_spec.entryCount);

            // There can only be 2^16 entries in a type, because that is the ID
            // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
            // if (entry_count > std.numeric_limits<short>.max()) {
            if (entry_count > 0xffff) {
              logError("RES_TABLE_TYPE_SPEC_TYPE has too many entries (" + entry_count + ").");
              return emptyBraces();
            }

            if (entry_count * 4 /*sizeof(int)*/ > chunk.data_size()) {
              logError("RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.");
              return emptyBraces();
            }

            // If this is an overlay, associate the mapping of this type to the target type
            // from the IDMAP.
            IdmapEntry_header idmap_entry_header = null;
            if (loaded_idmap != null) {
              idmap_entry_header = loaded_idmap.GetEntryMapForType(type_spec.id);
            }

            TypeSpecPtrBuilder builder_ptr = type_builder_map.get(type_spec.id - 1);
            if (builder_ptr == null) {
              // builder_ptr = util.make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
              builder_ptr = new TypeSpecPtrBuilder(type_spec, idmap_entry_header);
              type_builder_map.put(type_spec.id - 1, builder_ptr);
            } else {
              logWarning(String.format("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
                  type_spec.id));
            }
          } break;

          case RES_TABLE_TYPE_TYPE: {
            // ResTable_type type = child_chunk.header<ResTable_type, kResTableTypeMinSize>();
            ResTable_type type = child_chunk.asResTable_type(kResTableTypeMinSize);
            if (type == null) {
              logError("RES_TABLE_TYPE_TYPE too small.");
              return emptyBraces();
            }

            if (!VerifyResTableType(type)) {
              return emptyBraces();
            }

            // Type chunks must be preceded by their TypeSpec chunks.
            TypeSpecPtrBuilder builder_ptr = type_builder_map.get(type.id - 1);
            if (builder_ptr != null) {
              builder_ptr.AddType(type);
            } else {
              logError(String.format(
                  "RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
                  type.id));
              return emptyBraces();
            }
          } break;

          case RES_TABLE_LIBRARY_TYPE: {
            ResTable_lib_header lib = child_chunk.asResTable_lib_header();
            if (lib == null) {
              logError("RES_TABLE_LIBRARY_TYPE too small.");
              return emptyBraces();
            }

            if (child_chunk.data_size() / ResTable_lib_entry.SIZEOF < dtohl(lib.count)) {
              logError("RES_TABLE_LIBRARY_TYPE too small to hold entries.");
              return emptyBraces();
            }

            // loaded_package.dynamic_package_map_.reserve(dtohl(lib.count));

            // ResTable_lib_entry entry_begin =
            //     reinterpret_cast<ResTable_lib_entry*>(child_chunk.data_ptr());
            ResTable_lib_entry entry_begin =
                child_chunk.asResTable_lib_entry();
            // ResTable_lib_entry entry_end = entry_begin + dtohl(lib.count);
            // for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
            for (ResTable_lib_entry entry_iter = entry_begin;
                entry_iter.myOffset() != entry_begin.myOffset() + dtohl(lib.count);
                entry_iter = new ResTable_lib_entry(entry_iter.myBuf(), entry_iter.myOffset() + ResTable_lib_entry.SIZEOF)) {
              String package_name =
                  Util.ReadUtf16StringFromDevice(entry_iter.packageName,
                      entry_iter.packageName.length);

              if (dtohl(entry_iter.packageId) >= 255) {
                logError(String.format(
                    "Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.",
                    dtohl(entry_iter.packageId), package_name));
                return emptyBraces();
              }

              // loaded_package.dynamic_package_map_.emplace_back(std.move(package_name),
              //     dtohl(entry_iter.packageId));
              loaded_package.dynamic_package_map_.add(new DynamicPackageEntry(package_name,
                  dtohl(entry_iter.packageId)));
            }

          } break;

          default:
            logWarning(String.format("Unknown chunk type '%02x'.", chunk.type()));
            break;
        }
      }

      if (iter.HadError()) {
        logError(iter.GetLastError());
        if (iter.HadFatalError()) {
          return emptyBraces();
        }
      }

      // Flatten and construct the TypeSpecs.
      for (Entry<Integer, TypeSpecPtrBuilder> entry : type_builder_map.entrySet()) {
        byte type_idx = (byte) entry.getKey().byteValue();
        TypeSpec type_spec_ptr = entry.getValue().Build();
        if (type_spec_ptr == null) {
          logError("Too many type configurations, overflow detected.");
          return emptyBraces();
        }

        // We only add the type to the package if there is no IDMAP, or if the type is
        // overlaying something.
        if (loaded_idmap == null || type_spec_ptr.idmap_entries != null) {
          // If this is an overlay, insert it at the target type ID.
          if (type_spec_ptr.idmap_entries != null) {
            type_idx = (byte) (dtohs(type_spec_ptr.idmap_entries.target_type_id) - 1);
          }
          // loaded_package.type_specs_.editItemAt(type_idx) = std.move(type_spec_ptr);
          loaded_package.type_specs_.put((int) type_idx, type_spec_ptr);
        }
      }

      // return std.move(loaded_package);
      return loaded_package;
    }

    // Returns the string pool where type names are stored.
    ResStringPool GetTypeStringPool() {
      return type_string_pool_;
    }

    // Returns the string pool where the names of resource entries are stored.
    ResStringPool GetKeyStringPool() {
      return key_string_pool_;
    }

    String GetPackageName() {
      return package_name_;
    }

    int GetPackageId() {
      return package_id_;
    }

    // Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
    boolean IsDynamic() {
      return dynamic_;
    }

    // Returns true if this package originates from a system provided resource.
    boolean IsSystem() {
      return system_;
    }

    // Returns true if this package is from an overlay ApkAssets.
    boolean IsOverlay() {
      return overlay_;
    }

    // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
    // package could have been assigned a different package ID than what this LoadedPackage was
    // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
    List<DynamicPackageEntry> GetDynamicPackageMap() {
      return dynamic_package_map_;
    }

    // type_idx is TT - 1 from 0xPPTTEEEE.
    TypeSpec GetTypeSpecByTypeIndex(int type_index) {
      // If the type IDs are offset in this package, we need to take that into account when searching
      // for a type.
      return type_specs_.get(type_index - type_id_offset_);
    }

    // template <typename Func>
    interface TypeSpecFunc {
      void apply(TypeSpec spec, byte index);
    }

    void ForEachTypeSpec(TypeSpecFunc f) {
      for (Integer i : type_specs_.keySet()) {
        TypeSpec ptr = type_specs_.get(i);
        if (ptr != null) {
          byte type_id = ptr.type_spec.id;
          if (ptr.idmap_entries != null) {
            type_id = (byte) ptr.idmap_entries.target_type_id;
          }
          f.apply(ptr, (byte) (type_id - 1));
        }
      }
    }

    private static LoadedPackage emptyBraces() {
      return new LoadedPackage();
    }
  }

  // Gets a pointer to the package with the specified package ID, or nullptr if no such package
  // exists.
  LoadedPackage GetPackageById(int package_id) {
    for (LoadedPackage loaded_package : packages_) {
      if (loaded_package.GetPackageId() == package_id) {
        return loaded_package;
      }
    }
    return null;
  }

  boolean LoadTable(Chunk chunk, LoadedIdmap loaded_idmap,
      boolean load_as_shared_library) {
    // ResTable_header header = chunk.header<ResTable_header>();
    ResTable_header header = chunk.asResTable_header();
    if (header == null) {
      logError("RES_TABLE_TYPE too small.");
      return false;
    }

    int package_count = dtohl(header.packageCount);
    int packages_seen = 0;

    // packages_.reserve(package_count);

    Chunk.Iterator iter = new Iterator(chunk.data_ptr(), chunk.data_size());
    while (iter.HasNext()) {
      Chunk child_chunk = iter.Next();
      switch (child_chunk.type()) {
        case RES_STRING_POOL_TYPE:
          // Only use the first string pool. Ignore others.
          if (global_string_pool_.getError() == NO_INIT) {
            ResStringPool_header resStringPool_header = child_chunk.asResStringPool_header();
            int err = global_string_pool_.setTo(resStringPool_header.myBuf(),
                resStringPool_header.myOffset(),
                child_chunk.size(), false);
            if (err != NO_ERROR) {
              logError("RES_STRING_POOL_TYPE corrupt.");
              return false;
            }
          } else {
            logWarning("Multiple RES_STRING_POOL_TYPEs found in RES_TABLE_TYPE.");
          }
          break;

        case RES_TABLE_PACKAGE_TYPE: {
          if (packages_seen + 1 > package_count) {
            logError("More package chunks were found than the " + package_count
                + " declared in the header.");
            return false;
          }
          packages_seen++;

          LoadedPackage loaded_package =
              LoadedPackage.Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
          if (!isTruthy(loaded_package)) {
            return false;
          }
          packages_.add(loaded_package);
        } break;

        default:
          logWarning(String.format("Unknown chunk type '%02x'.", chunk.type()));
          break;
      }
    }

    if (iter.HadError()) {
      logError(iter.GetLastError());
      if (iter.HadFatalError()) {
        return false;
      }
    }
    return true;
  }

  // Read-only view into a resource table. This class validates all data
// when loading, including offsets and lengths.
//class LoadedArsc {
// public:
  // Load a resource table from memory pointed to by `data` of size `len`.
  // The lifetime of `data` must out-live the LoadedArsc returned from this method.
  // If `system` is set to true, the LoadedArsc is considered as a system provided resource.
  // If `load_as_shared_library` is set to true, the application package (0x7f) is treated
  // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an
  // ID.
  static LoadedArsc Load(StringPiece data,
      LoadedIdmap loaded_idmap /* = null */, boolean system /* = false */,
      boolean load_as_shared_library /* = false */) {
    // ATRACE_NAME("LoadedArsc::LoadTable");

    // Not using make_unique because the constructor is private.
    LoadedArsc loaded_arsc = new LoadedArsc();
    loaded_arsc.system_ = system;

    Chunk.Iterator iter = new Iterator(data, data.size());
    while (iter.HasNext()) {
      Chunk chunk = iter.Next();
      switch (chunk.type()) {
        case RES_TABLE_TYPE:
          if (!loaded_arsc.LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
            return emptyBraces();
          }
          break;

        default:
          logWarning(String.format("Unknown chunk type '%02x'.", chunk.type()));
          break;
      }
    }

    if (iter.HadError()) {
      logError(iter.GetLastError());
      if (iter.HadFatalError()) {
        return emptyBraces();
      }
    }

    // Need to force a move for mingw32.
    // return std.move(loaded_arsc);
    return loaded_arsc;
  }

  // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
  static LoadedArsc CreateEmpty() {
    return new LoadedArsc();
  }

  // Populates a set of ResTable_config structs, possibly excluding configurations defined for
  // the mipmap type.
  // void CollectConfigurations(boolean exclude_mipmap, Set<ResTable_config> out_configs);

  // Populates a set of strings representing locales.
  // If `canonicalize` is set to true, each locale is transformed into its canonical format
  // before being inserted into the set. This may cause some equivalent locales to de-dupe.
  // void CollectLocales(boolean canonicalize, Set<String> out_locales);

  private static LoadedArsc emptyBraces() {
    return new LoadedArsc();
  }

}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.shadow.api.Shadow.extract;
import static org.robolectric.shadows.ShadowPath.Point.Type.LINE_TO;
import static org.robolectric.shadows.ShadowPath.Point.Type.MOVE_TO;

import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.RectF;
import android.util.Log;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;

/**
 * The shadow only supports straight-line paths.
 */
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Path.class)
public class ShadowPath {
  private static final String TAG = ShadowPath.class.getSimpleName();
  private static final float EPSILON = 1e-4f;

  @RealObject private Path realObject;

  private List<Point> points = new ArrayList<>();
  private Point wasMovedTo;

  private float mLastX = 0;
  private float mLastY = 0;
  private Path2D mPath = new Path2D.Double();
  private boolean mCachedIsEmpty = true;
  private Path.FillType mFillType = Path.FillType.WINDING;
  protected boolean isSimplePath;

  @Implementation
  protected void __constructor__(Path path) {
    ShadowPath shadowPath = extract(path);
    points = new ArrayList<>(shadowPath.getPoints());
  }

  Path2D getJavaShape() {
    return mPath;
  }

  @Implementation
  protected void moveTo(float x, float y) {
    mPath.moveTo(mLastX = x, mLastY = y);

    // Legacy recording behavior
    Point p = new Point(x, y, MOVE_TO);
    points.add(p);
  }

  @Implementation
  protected void lineTo(float x, float y) {
    if (!hasPoints()) {
      mPath.moveTo(mLastX = 0, mLastY = 0);
    }
    mPath.lineTo(mLastX = x, mLastY = y);

    // Legacy recording behavior
    Point point = new Point(x, y, LINE_TO);
    points.add(point);
  }

  @Implementation
  protected void quadTo(float x1, float y1, float x2, float y2) {
    isSimplePath = false;
    if (!hasPoints()) {
      moveTo(0, 0);
    }
    mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
  }

  @Implementation
  protected void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
    if (!hasPoints()) {
      mPath.moveTo(0, 0);
    }
    mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
  }

  private boolean hasPoints() {
    return !mPath.getPathIterator(null).isDone();
  }

  @Implementation
  protected void reset() {
    mPath.reset();
    mLastX = 0;
    mLastY = 0;

    // Legacy recording behavior
    points.clear();
  }

  @Implementation(minSdk = LOLLIPOP)
  protected float[] approximate(float acceptableError) {
    PathIterator iterator = mPath.getPathIterator(null, acceptableError);

    float segment[] = new float[6];
    float totalLength = 0;
    ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
    Point2D.Float previousPoint = null;
    while (!iterator.isDone()) {
      int type = iterator.currentSegment(segment);
      Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
      // MoveTo shouldn't affect the length
      if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
        totalLength += (float) currentPoint.distance(previousPoint);
      }
      previousPoint = currentPoint;
      points.add(currentPoint);
      iterator.next();
    }

    int nPoints = points.size();
    float[] result = new float[nPoints * 3];
    previousPoint = null;
    // Distance that we've covered so far. Used to calculate the fraction of the path that
    // we've covered up to this point.
    float walkedDistance = .0f;
    for (int i = 0; i < nPoints; i++) {
      Point2D.Float point = points.get(i);
      float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
      walkedDistance += distance;
      result[i * 3] = walkedDistance / totalLength;
      result[i * 3 + 1] = point.x;
      result[i * 3 + 2] = point.y;

      previousPoint = point;
    }

    return result;
  }

  /**
   * @return all the points that have been added to the {@code Path}
   */
  public List<Point> getPoints() {
    return points;
  }

  public static class Point {
    private final float x;
    private final float y;
    private final Type type;

    public enum Type {
      MOVE_TO,
      LINE_TO
    }

    public Point(float x, float y, Type type) {
      this.x = x;
      this.y = y;
      this.type = type;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof Point)) return false;

      Point point = (Point) o;

      if (Float.compare(point.x, x) != 0) return false;
      if (Float.compare(point.y, y) != 0) return false;
      if (type != point.type) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = (x != +0.0f ? Float.floatToIntBits(x) : 0);
      result = 31 * result + (y != +0.0f ? Float.floatToIntBits(y) : 0);
      result = 31 * result + (type != null ? type.hashCode() : 0);
      return result;
    }

    @Override
    public String toString() {
      return "Point(" + x + "," + y + "," + type + ")";
    }

    public float getX() {
      return x;
    }

    public float getY() {
      return y;
    }

    public Type getType() {
      return type;
    }
  }

  @Implementation
  protected void rewind() {
    // call out to reset since there's nothing to optimize in
    // terms of data structs.
    reset();
  }

  @Implementation
  protected void set(Path src) {
    mPath.reset();

    ShadowPath shadowSrc = extract(src);
    setFillType(shadowSrc.mFillType);
    mPath.append(shadowSrc.mPath, false /*connect*/);
  }

  @Implementation(minSdk = KITKAT)
  protected boolean op(Path path1, Path path2, Path.Op op) {
    Log.w(TAG, "android.graphics.Path#op() not supported yet.");
    return false;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected boolean isConvex() {
    Log.w(TAG, "android.graphics.Path#isConvex() not supported yet.");
    return true;
  }

  @Implementation
  protected Path.FillType getFillType() {
    return mFillType;
  }

  @Implementation
  protected void setFillType(Path.FillType fillType) {
    mFillType = fillType;
    mPath.setWindingRule(getWindingRule(fillType));
  }

  /**
   * Returns the Java2D winding rules matching a given Android {@link FillType}.
   *
   * @param type the android fill type
   * @return the matching java2d winding rule.
   */
  private static int getWindingRule(Path.FillType type) {
    switch (type) {
      case WINDING:
      case INVERSE_WINDING:
        return GeneralPath.WIND_NON_ZERO;
      case EVEN_ODD:
      case INVERSE_EVEN_ODD:
        return GeneralPath.WIND_EVEN_ODD;

      default:
        assert false;
        return GeneralPath.WIND_NON_ZERO;
    }
  }

  @Implementation
  protected boolean isInverseFillType() {
    throw new UnsupportedOperationException("isInverseFillType");
  }

  @Implementation
  protected void toggleInverseFillType() {
    throw new UnsupportedOperationException("toggleInverseFillType");
  }

  @Implementation
  protected boolean isEmpty() {
    if (!mCachedIsEmpty) {
      return false;
    }

    float[] coords = new float[6];
    mCachedIsEmpty = Boolean.TRUE;
    for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
      int type = it.currentSegment(coords);
      // if (type != PathIterator.SEG_MOVETO) {
      // Once we know that the path is not empty, we do not need to check again unless
      // Path#reset is called.
      mCachedIsEmpty = false;
      return false;
      // }
    }

    return true;
  }

  @Implementation
  protected boolean isRect(RectF rect) {
    // create an Area that can test if the path is a rect
    Area area = new Area(mPath);
    if (area.isRectangular()) {
      if (rect != null) {
        fillBounds(rect);
      }

      return true;
    }

    return false;
  }

  @Implementation
  protected void computeBounds(RectF bounds, boolean exact) {
    fillBounds(bounds);
  }

  @Implementation
  protected void incReserve(int extraPtCount) {
    throw new UnsupportedOperationException("incReserve");
  }

  @Implementation
  protected void rMoveTo(float dx, float dy) {
    dx += mLastX;
    dy += mLastY;
    mPath.moveTo(mLastX = dx, mLastY = dy);
  }

  @Implementation
  protected void rLineTo(float dx, float dy) {
    if (!hasPoints()) {
      mPath.moveTo(mLastX = 0, mLastY = 0);
    }

    if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
      // The delta is so small that this shouldn't generate a line
      return;
    }

    dx += mLastX;
    dy += mLastY;
    mPath.lineTo(mLastX = dx, mLastY = dy);
  }

  @Implementation
  protected void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
    if (!hasPoints()) {
      mPath.moveTo(mLastX = 0, mLastY = 0);
    }
    dx1 += mLastX;
    dy1 += mLastY;
    dx2 += mLastX;
    dy2 += mLastY;
    mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
  }

  @Implementation
  protected void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
    if (!hasPoints()) {
      mPath.moveTo(mLastX = 0, mLastY = 0);
    }
    x1 += mLastX;
    y1 += mLastY;
    x2 += mLastX;
    y2 += mLastY;
    x3 += mLastX;
    y3 += mLastY;
    mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
  }

  @Implementation
  protected void arcTo(RectF oval, float startAngle, float sweepAngle) {
    arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
  }

  @Implementation
  protected void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
    arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
  }

  @Implementation(minSdk = LOLLIPOP)
  protected void arcTo(
      float left,
      float top,
      float right,
      float bottom,
      float startAngle,
      float sweepAngle,
      boolean forceMoveTo) {
    isSimplePath = false;
    Arc2D arc =
        new Arc2D.Float(
            left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN);
    mPath.append(arc, true /*connect*/);

    resetLastPointFromPath();
  }

  @Implementation
  protected void close() {
    if (!hasPoints()) {
      mPath.moveTo(mLastX = 0, mLastY = 0);
    }
    mPath.closePath();
  }

  @Implementation
  protected void addRect(RectF rect, Direction dir) {
    addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
  }

  @Implementation
  protected void addRect(float left, float top, float right, float bottom, Path.Direction dir) {
    moveTo(left, top);

    switch (dir) {
      case CW:
        lineTo(right, top);
        lineTo(right, bottom);
        lineTo(left, bottom);
        break;
      case CCW:
        lineTo(left, bottom);
        lineTo(right, bottom);
        lineTo(right, top);
        break;
    }

    close();

    resetLastPointFromPath();
  }

  @Implementation(minSdk = LOLLIPOP)
  protected void addOval(float left, float top, float right, float bottom, Path.Direction dir) {
    mPath.append(new Ellipse2D.Float(left, top, right - left, bottom - top), false);
  }

  @Implementation
  protected void addCircle(float x, float y, float radius, Path.Direction dir) {
    mPath.append(new Ellipse2D.Float(x - radius, y - radius, radius * 2, radius * 2), false);
  }

  @Implementation(minSdk = LOLLIPOP)
  protected void addArc(
      float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
    mPath.append(
        new Arc2D.Float(
            left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN),
        false);
  }

  @Implementation(minSdk = JELLY_BEAN)
  protected void addRoundRect(RectF rect, float rx, float ry, Direction dir) {
    addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir);
  }

  @Implementation(minSdk = JELLY_BEAN)
  protected void addRoundRect(RectF rect, float[] radii, Direction dir) {
    if (rect == null) {
      throw new NullPointerException("need rect parameter");
    }
    addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
  }

  @Implementation(minSdk = LOLLIPOP)
  protected void addRoundRect(
      float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir) {
    mPath.append(
        new RoundRectangle2D.Float(left, top, right - left, bottom - top, rx * 2, ry * 2), false);
  }

  @Implementation(minSdk = LOLLIPOP)
  protected void addRoundRect(
      float left, float top, float right, float bottom, float[] radii, Path.Direction dir) {
    if (radii.length < 8) {
      throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
    }
    isSimplePath = false;

    float[] cornerDimensions = new float[radii.length];
    for (int i = 0; i < radii.length; i++) {
      cornerDimensions[i] = 2 * radii[i];
    }
    mPath.append(
        new RoundRectangle(left, top, right - left, bottom - top, cornerDimensions), false);
  }

  @Implementation
  protected void addPath(Path src, float dx, float dy) {
    isSimplePath = false;
    ShadowPath.addPath(realObject, src, AffineTransform.getTranslateInstance(dx, dy));
  }

  @Implementation
  protected void addPath(Path src) {
    isSimplePath = false;
    ShadowPath.addPath(realObject, src, null);
  }

  @Implementation
  protected void addPath(Path src, Matrix matrix) {
    if (matrix == null) {
      return;
    }
    ShadowPath shadowSrc = extract(src);
    if (!shadowSrc.isSimplePath) isSimplePath = false;

    ShadowMatrix shadowMatrix = extract(matrix);
    ShadowPath.addPath(realObject, src, shadowMatrix.getAffineTransform());
  }

  private static void addPath(Path destPath, Path srcPath, AffineTransform transform) {
    if (destPath == null) {
      return;
    }

    if (srcPath == null) {
      return;
    }

    ShadowPath shadowDestPath = extract(destPath);
    ShadowPath shadowSrcPath = extract(srcPath);
    if (transform != null) {
      shadowDestPath.mPath.append(shadowSrcPath.mPath.getPathIterator(transform), false);
    } else {
      shadowDestPath.mPath.append(shadowSrcPath.mPath, false);
    }
  }

  @Implementation
  protected void offset(float dx, float dy, Path dst) {
    if (dst != null) {
      dst.set(realObject);
    } else {
      dst = realObject;
    }
    dst.offset(dx, dy);
  }

  @Implementation
  protected void offset(float dx, float dy) {
    GeneralPath newPath = new GeneralPath();

    PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));

    newPath.append(iterator, false /*connect*/);
    mPath = newPath;
  }

  @Implementation
  protected void setLastPoint(float dx, float dy) {
    mLastX = dx;
    mLastY = dy;
  }

  @Implementation
  protected void transform(Matrix matrix, Path dst) {
    ShadowMatrix shadowMatrix = extract(matrix);

    if (shadowMatrix.hasPerspective()) {
      Log.w(TAG, "android.graphics.Path#transform() only supports affine transformations.");
    }

    GeneralPath newPath = new GeneralPath();

    PathIterator iterator = mPath.getPathIterator(shadowMatrix.getAffineTransform());
    newPath.append(iterator, false /*connect*/);

    if (dst != null) {
      ShadowPath shadowPath = extract(dst);
      shadowPath.mPath = newPath;
    } else {
      mPath = newPath;
    }
  }

  @Implementation
  protected void transform(Matrix matrix) {
    transform(matrix, null);
  }

  /**
   * Fills the given {@link RectF} with the path bounds.
   *
   * @param bounds the RectF to be filled.
   */
  public void fillBounds(RectF bounds) {
    Rectangle2D rect = mPath.getBounds2D();
    bounds.left = (float) rect.getMinX();
    bounds.right = (float) rect.getMaxX();
    bounds.top = (float) rect.getMinY();
    bounds.bottom = (float) rect.getMaxY();
  }

  private void resetLastPointFromPath() {
    Point2D last = mPath.getCurrentPoint();
    mLastX = (float) last.getX();
    mLastY = (float) last.getY();
  }
}


package org.robolectric.shadows;

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

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.util.Pair;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
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.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

/**
 * Shadow for {@link android.net.wifi.WifiManager}.
 */
@Implements(WifiManager.class)
public class ShadowWifiManager {
  private static final int LOCAL_HOST = 2130706433;

  private static float sSignalLevelInPercent = 1f;
  private boolean accessWifiStatePermission = true;
  private boolean wifiEnabled = true;
  private boolean wasSaved = false;
  private WifiInfo wifiInfo;
  private List<ScanResult> scanResults;
  private final Map<Integer, WifiConfiguration> networkIdToConfiguredNetworks = new LinkedHashMap<>();
  private Pair<Integer, Boolean> lastEnabledNetwork;
  private DhcpInfo dhcpInfo;
  private boolean isScanAlwaysAvailable = true;
  private boolean startScanSucceeds = true;
  private boolean is5GHzBandSupported = false;
  private AtomicInteger activeLockCount = new AtomicInteger(0);
  private final BitSet readOnlyNetworkIds = new BitSet();
  @RealObject WifiManager wifiManager;

  @Implementation
  protected boolean setWifiEnabled(boolean wifiEnabled) {
    checkAccessWifiStatePermission();
    this.wifiEnabled = wifiEnabled;
    return true;
  }

  @Implementation
  protected boolean isWifiEnabled() {
    checkAccessWifiStatePermission();
    return wifiEnabled;
  }

  @Implementation
  protected int getWifiState() {
    if (isWifiEnabled()) {
      return WifiManager.WIFI_STATE_ENABLED;
    } else {
      return WifiManager.WIFI_STATE_DISABLED;
    }
  }

  @Implementation
  protected WifiInfo getConnectionInfo() {
    checkAccessWifiStatePermission();
    if (wifiInfo == null) {
      wifiInfo = ReflectionHelpers.callConstructor(WifiInfo.class);
    }
    return wifiInfo;
  }

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

  /** Sets whether 5ghz band is supported. */
  public void setIs5GHzBandSupported(boolean is5GHzBandSupported) {
    this.is5GHzBandSupported = is5GHzBandSupported;
  }

  /**
   * Sets the connection info as the provided {@link WifiInfo}.
   */
  public void setConnectionInfo(WifiInfo wifiInfo) {
    this.wifiInfo = wifiInfo;
  }

  /** Sets the return value of {@link #startScan}. */
  public void setStartScanSucceeds(boolean succeeds) {
    this.startScanSucceeds = succeeds;
  }

  @Implementation
  protected List<ScanResult> getScanResults() {
    return scanResults;
  }

  @Implementation
  protected List<WifiConfiguration> getConfiguredNetworks() {
    final ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
    for (WifiConfiguration wifiConfiguration : networkIdToConfiguredNetworks.values()) {
      wifiConfigurations.add(wifiConfiguration);
    }
    return wifiConfigurations;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
    return getConfiguredNetworks();
  }

  @Implementation
  protected int addNetwork(WifiConfiguration config) {
    int networkId = networkIdToConfiguredNetworks.size();
    config.networkId = -1;
    networkIdToConfiguredNetworks.put(networkId, makeCopy(config, networkId));
    return networkId;
  }

  @Implementation
  protected boolean removeNetwork(int netId) {
    networkIdToConfiguredNetworks.remove(netId);
    return true;
  }

  /**
   * Adds or updates a network which can later be retrieved with {@link #getWifiConfiguration(int)}
   * method. A null {@param config}, or one with a networkId less than 0, or a networkId that had
   * its updatePermission removed using the {@link #setUpdateNetworkPermission(int, boolean)} will
   * return -1, which indicates a failure to update.
   */
  @Implementation
  protected int updateNetwork(WifiConfiguration config) {
    if (config == null || config.networkId < 0 || readOnlyNetworkIds.get(config.networkId)) {
      return -1;
    }
    networkIdToConfiguredNetworks.put(config.networkId, makeCopy(config, config.networkId));
    return config.networkId;
  }

  @Implementation
  protected boolean saveConfiguration() {
    wasSaved = true;
    return true;
  }

  @Implementation
  protected boolean enableNetwork(int netId, boolean disableOthers) {
    lastEnabledNetwork = new Pair<>(netId, disableOthers);
    return true;
  }

  @Implementation
  protected WifiManager.WifiLock createWifiLock(int lockType, String tag) {
    WifiManager.WifiLock wifiLock = ReflectionHelpers.callConstructor(WifiManager.WifiLock.class);
    shadowOf(wifiLock).setWifiManager(wifiManager);
    return wifiLock;
  }

  @Implementation
  protected WifiManager.WifiLock createWifiLock(String tag) {
    return createWifiLock(WifiManager.WIFI_MODE_FULL, tag);
  }

  @Implementation
  protected MulticastLock createMulticastLock(String tag) {
    MulticastLock multicastLock = ReflectionHelpers.callConstructor(MulticastLock.class);
    shadowOf(multicastLock).setWifiManager(wifiManager);
    return multicastLock;
  }

  @Implementation
  protected static int calculateSignalLevel(int rssi, int numLevels) {
    return (int) (sSignalLevelInPercent * (numLevels - 1));
  }

  /**
   * Does nothing and returns the configured success status.
   *
   * <p>That is different from the Android implementation which always returns {@code true} up to
   * and including Android 8, and either {@code true} or {@code false} on Android 9+.
   *
   * @return the value configured by {@link #setStartScanSucceeds}, or {@code true} if that method
   *     was never called.
   */
  @Implementation
  protected boolean startScan() {
    return startScanSucceeds;
  }

  @Implementation
  protected DhcpInfo getDhcpInfo() {
    return dhcpInfo;
  }

  @Implementation(minSdk = JELLY_BEAN_MR2)
  protected boolean isScanAlwaysAvailable() {
    return isScanAlwaysAvailable;
  }

  @HiddenApi
  @Implementation(minSdk = KITKAT)
  protected void connect(WifiConfiguration wifiConfiguration, WifiManager.ActionListener listener) {
    WifiInfo wifiInfo = getConnectionInfo();

    String ssid = isQuoted(wifiConfiguration.SSID)
        ? stripQuotes(wifiConfiguration.SSID)
        : wifiConfiguration.SSID;

    ShadowWifiInfo shadowWifiInfo = Shadow.extract(wifiInfo);
    shadowWifiInfo.setSSID(ssid);
    shadowWifiInfo.setBSSID(wifiConfiguration.BSSID);
    shadowWifiInfo.setNetworkId(wifiConfiguration.networkId);
    setConnectionInfo(wifiInfo);

    // Now that we're "connected" to wifi, update Dhcp and point it to localhost.
    DhcpInfo dhcpInfo = new DhcpInfo();
    dhcpInfo.gateway = LOCAL_HOST;
    dhcpInfo.ipAddress = LOCAL_HOST;
    setDhcpInfo(dhcpInfo);

    // Now add the network to ConnectivityManager.
    NetworkInfo networkInfo =
        ShadowNetworkInfo.newInstance(
            NetworkInfo.DetailedState.CONNECTED,
            ConnectivityManager.TYPE_WIFI,
            0 /* subType */,
            true /* isAvailable */,
            true /* isConnected */);
    ShadowConnectivityManager connectivityManager =
        Shadow.extract(
                    RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE));
    connectivityManager.setActiveNetworkInfo(networkInfo);

    if (listener != null) {
      listener.onSuccess();
    }
  }

  @HiddenApi
  @Implementation(minSdk = KITKAT)
  protected void connect(int networkId, WifiManager.ActionListener listener) {
    WifiConfiguration wifiConfiguration = new WifiConfiguration();
    wifiConfiguration.networkId = networkId;
    wifiConfiguration.SSID = "";
    wifiConfiguration.BSSID = "";
    connect(wifiConfiguration, listener);
  }

  private static boolean isQuoted(String str) {
    if (str == null || str.length() < 2) {
      return false;
    }

    return str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"';
  }

  private static String stripQuotes(String str) {
    return str.substring(1, str.length() - 1);
  }

  @Implementation
  protected boolean reconnect() {
    WifiConfiguration wifiConfiguration = getMostRecentNetwork();
    if (wifiConfiguration == null) {
      return false;
    }

    connect(wifiConfiguration, null);
    return true;
  }

  private WifiConfiguration getMostRecentNetwork() {
    if (getLastEnabledNetwork() == null) {
      return null;
    }

    return getWifiConfiguration(getLastEnabledNetwork().first);
  }

  public static void setSignalLevelInPercent(float level) {
    if (level < 0 || level > 1) {
      throw new IllegalArgumentException("level needs to be between 0 and 1");
    }
    sSignalLevelInPercent = level;
  }

  public void setAccessWifiStatePermission(boolean accessWifiStatePermission) {
    this.accessWifiStatePermission = accessWifiStatePermission;
  }

  /**
   * Prevents a networkId from being updated using the {@link updateNetwork(WifiConfiguration)}
   * method. This is to simulate the case where a separate application creates a network, and the
   * Android security model prevents your application from updating it.
   */
  public void setUpdateNetworkPermission(int networkId, boolean hasPermission) {
    readOnlyNetworkIds.set(networkId, !hasPermission);
  }

  public void setScanResults(List<ScanResult> scanResults) {
    this.scanResults = scanResults;
  }

  public void setDhcpInfo(DhcpInfo dhcpInfo) {
    this.dhcpInfo = dhcpInfo;
  }

  public Pair<Integer, Boolean> getLastEnabledNetwork() {
    return lastEnabledNetwork;
  }

  /** Returns the number of WifiLocks and MulticastLocks that are currently acquired. */
  public int getActiveLockCount() {
    return activeLockCount.get();
  }

  public boolean wasConfigurationSaved() {
    return wasSaved;
  }

  public void setIsScanAlwaysAvailable(boolean isScanAlwaysAvailable) {
    this.isScanAlwaysAvailable = isScanAlwaysAvailable;
  }

  private void checkAccessWifiStatePermission() {
    if (!accessWifiStatePermission) {
      throw new SecurityException();
    }
  }

  private WifiConfiguration makeCopy(WifiConfiguration config, int networkId) {
    ShadowWifiConfiguration shadowWifiConfiguration = Shadow.extract(config);
    WifiConfiguration copy = shadowWifiConfiguration.copy();
    copy.networkId = networkId;
    return copy;
  }

  public WifiConfiguration getWifiConfiguration(int netId) {
    return networkIdToConfiguredNetworks.get(netId);
  }

  @Implements(WifiManager.WifiLock.class)
  public static class ShadowWifiLock {
    private int refCount;
    private boolean refCounted = true;
    private boolean locked;
    private WifiManager wifiManager;
    public static final int MAX_ACTIVE_LOCKS = 50;

    private void setWifiManager(WifiManager wifiManager) {
      this.wifiManager = wifiManager;
    }

    @Implementation
    protected synchronized void acquire() {
      if (wifiManager != null) {
        shadowOf(wifiManager).activeLockCount.getAndIncrement();
      }
      if (refCounted) {
        if (++refCount >= MAX_ACTIVE_LOCKS) throw new UnsupportedOperationException("Exceeded maximum number of wifi locks");
      } else {
        locked = true;
      }
    }

    @Implementation
    protected synchronized void release() {
      if (wifiManager != null) {
        shadowOf(wifiManager).activeLockCount.getAndDecrement();
      }
      if (refCounted) {
        if (--refCount < 0) throw new RuntimeException("WifiLock under-locked");
      } else {
        locked = false;
      }
    }

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

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

  @Implements(MulticastLock.class)
  public static class ShadowMulticastLock {
    private int refCount;
    private boolean refCounted = true;
    private boolean locked;
    static final int MAX_ACTIVE_LOCKS = 50;
    private WifiManager wifiManager;

    private void setWifiManager(WifiManager wifiManager) {
      this.wifiManager = wifiManager;
    }

    @Implementation
    protected void acquire() {
      if (wifiManager != null) {
        shadowOf(wifiManager).activeLockCount.getAndIncrement();
      }
      if (refCounted) {
        if (++refCount >= MAX_ACTIVE_LOCKS) throw new UnsupportedOperationException("Exceeded maximum number of wifi locks");
      } else {
        locked = true;
      }
    }

    @Implementation
    protected synchronized void release() {
      if (wifiManager != null) {
        shadowOf(wifiManager).activeLockCount.getAndDecrement();
      }
      if (refCounted) {
        if (--refCount < 0) throw new RuntimeException("WifiLock under-locked");
      } else {
        locked = false;
      }
    }

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

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

  private static ShadowWifiLock shadowOf(WifiManager.WifiLock o) {
    return Shadow.extract(o);
  }

  private static ShadowMulticastLock shadowOf(WifiManager.MulticastLock o) {
    return Shadow.extract(o);
  }

  private static ShadowWifiManager shadowOf(WifiManager o) {
    return Shadow.extract(o);
  }
}


package org.robolectric.res.android;

// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/ApkAssets.h
// and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ApkAssets.cpp

import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeDirectory;
import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeRegular;
import static org.robolectric.res.android.Util.CHECK;
import static org.robolectric.res.android.ZipFileRO.OpenArchive;
import static org.robolectric.res.android.ZipFileRO.kCompressDeflated;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import org.robolectric.res.android.Asset.AccessMode;
import org.robolectric.res.android.CppAssetManager.FileType;
import org.robolectric.res.android.Idmap.LoadedIdmap;
import org.robolectric.res.android.ZipFileRO.ZipEntryRO;
import org.robolectric.util.PerfStatsCollector;

//
// #ifndef APKASSETS_H_
// #define APKASSETS_H_
//
// #include <memory>
// #include <string>
//
// #include "android-base/macros.h"
// #include "ziparchive/zip_archive.h"
//
// #include "androidfw/Asset.h"
// #include "androidfw/LoadedArsc.h"
// #include "androidfw/misc.h"
//
// namespace android {
//
// // Holds an APK.
@SuppressWarnings("NewApi")
public class CppApkAssets {
  private static final String kResourcesArsc = "resources.arsc";
//  public:
//   static std::unique_ptr<const ApkAssets> Load(const String& path, bool system = false);
//   static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const String& path,
//                                                               bool system = false);
//
//   std::unique_ptr<Asset> Open(const String& path,
//                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
//
//   bool ForEachFile(const String& path,
//                    const std::function<void(const StringPiece&, FileType)>& f) const;

  public CppApkAssets(ZipArchiveHandle zip_handle_, String path_) {
    this.zip_handle_ = zip_handle_;
    this.path_ = path_;
  }

  public String GetPath() { return path_; }

  // This is never nullptr.
  public LoadedArsc GetLoadedArsc() {
    return loaded_arsc_;
  }

  //  private:
//   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
//
//   static std::unique_ptr<const ApkAssets> LoadImpl(const String& path, bool system,
//                                                    bool load_as_shared_library);
//
//   ApkAssets() = default;
//
//   struct ZipArchivePtrCloser {
//     void operator()(::ZipArchiveHandle handle) { ::CloseArchive(handle); }
//   };
//
//   using ZipArchivePtr =
//       std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;

  ZipArchiveHandle zip_handle_;
  private String path_;
  Asset resources_asset_;
  Asset idmap_asset_;
  private LoadedArsc loaded_arsc_;
  // };
//
// }  // namespace android
//
// #endif /* APKASSETS_H_ */
//
// #define ATRACE_TAG ATRACE_TAG_RESOURCES
//
// #include "androidfw/ApkAssets.h"
//
// #include <algorithm>
//
// #include "android-base/logging.h"
// #include "utils/FileMap.h"
// #include "utils/Trace.h"
// #include "ziparchive/zip_archive.h"
//
// #include "androidfw/Asset.h"
// #include "androidfw/Util.h"
//
// namespace android {
//
// Creates an ApkAssets.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
// std::unique_ptr<const ApkAssets> ApkAssets::Load(const String& path, bool system) {
  public static CppApkAssets Load(String path, boolean system) {
    return LoadImpl(/*{}*/-1 /*fd*/, path, null, null, system, false /*load_as_shared_library*/);
  }

  // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
  // If `system` is true, the package is marked as a system package, and allows some functions to
  // filter out this package when computing what configurations/resources are available.
// std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const String& path,
//                                                                 bool system) {
  public static CppApkAssets LoadAsSharedLibrary(String path,
      boolean system) {
    return LoadImpl(/*{}*/ -1 /*fd*/, path, null, null, system, true /*load_as_shared_library*/);
  }

  // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
  // data.
  // If `system` is true, the package is marked as a system package, and allows some functions to
  // filter out this package when computing what configurations/resources are available.
// std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
//                                                         bool system) {
  public static CppApkAssets LoadOverlay(String idmap_path,
      boolean system) {
    throw new UnsupportedOperationException();
    // Asset idmap_asset = CreateAssetFromFile(idmap_path);
    // if (idmap_asset == null) {
    //   return {};
    // }
    //
    // StringPiece idmap_data(
    //     reinterpret_cast<char*>(idmap_asset.getBuffer(true /*wordAligned*/)),
    //     static_cast<size_t>(idmap_asset.getLength()));
    // LoadedIdmap loaded_idmap = LoadedIdmap.Load(idmap_data);
    // if (loaded_idmap == null) {
    //   System.err.println( + "failed to load IDMAP " + idmap_path;
    //   return {};
    // }
    // return LoadImpl({} /*fd*/, loaded_idmap.OverlayApkPath(), std.move(idmap_asset),
    //     std.move(loaded_idmap), system, false /*load_as_shared_library*/);
  }

  // Creates an ApkAssets from the given file descriptor, and takes ownership of the file
  // descriptor. The `friendly_name` is some name that will be used to identify the source of
  // this ApkAssets in log messages and other debug scenarios.
  // If `system` is true, the package is marked as a system package, and allows some functions to
  // filter out this package when computing what configurations/resources are available.
  // If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
// std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
//                                                        const std::string& friendly_name,
//                                                        bool system, bool force_shared_lib) {
//   public static ApkAssets LoadFromFd(unique_fd fd,
//       String friendly_name,
//       boolean system, boolean force_shared_lib) {
//     return LoadImpl(std.move(fd), friendly_name, null /*idmap_asset*/, null /*loaded_idmap*/,
//         system, force_shared_lib);
//   }

  // std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
  static Asset CreateAssetFromFile(String path) {
    throw new UnsupportedOperationException();
    // unique_fd fd(base.utf8.open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
    // if (fd == -1) {
    //   System.err.println( + "Failed to open file '" + path + "': " + SystemErrorCodeToString(errno);
    //   return {};
    // }
    //
    // long file_len = lseek64(fd, 0, SEEK_END);
    // if (file_len < 0) {
    //   System.err.println( + "Failed to get size of file '" + path + "': " + SystemErrorCodeToString(errno);
    //   return {};
    // }
    //
    // std.unique_ptr<FileMap> file_map = util.make_unique<FileMap>();
    // if (!file_map.create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
    //   System.err.println( + "Failed to mmap file '" + path + "': " + SystemErrorCodeToString(errno);
    //   return {};
    // }
    // return Asset.createFromUncompressedMap(std.move(file_map), Asset.AccessMode.ACCESS_RANDOM);
  }

  /**
   * Measure performance implications of loading {@link CppApkAssets}.
   */
  static CppApkAssets LoadImpl(
      int fd, String path, Asset idmap_asset,
      LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library) {
    return PerfStatsCollector.getInstance()
        .measure(
            "load binary " + (system ? "framework" : "app") + " resources",
            () ->
                LoadImpl_measured(
                    fd, path, idmap_asset, loaded_idmap, system, load_as_shared_library));
  }

  // std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
  //     unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
  //     std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
  static CppApkAssets LoadImpl_measured(
      int fd, String path, Asset idmap_asset,
      LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library) {
    Ref<ZipArchiveHandle> unmanaged_handle = new Ref<>(null);
    int result;
    if (fd >= 0) {
      throw new UnsupportedOperationException();
      // result =
      //   OpenArchiveFd(fd.release(), path, &unmanaged_handle, true /*assume_ownership*/);
    } else {
      result = OpenArchive(path, unmanaged_handle);
    }

    if (result != 0) {
      System.err.println("Failed to open APK '" + path + "' " + ErrorCodeString(result));
      return null;
    }

    // Wrap the handle in a unique_ptr so it gets automatically closed.
    CppApkAssets loaded_apk = new CppApkAssets(unmanaged_handle.get(), path);

    // Find the resource table.
    String entry_name = kResourcesArsc;
    Ref<ZipEntry> entry = new Ref<>(null);
    // result = FindEntry(loaded_apk.zip_handle_.get(), entry_name, &entry);
    result = ZipFileRO.FindEntry(loaded_apk.zip_handle_, entry_name, entry);
    if (result != 0) {
      // There is no resources.arsc, so create an empty LoadedArsc and return.
      loaded_apk.loaded_arsc_ = LoadedArsc.CreateEmpty();
      return loaded_apk;
    }

    if (entry.get().getMethod() == kCompressDeflated) {
      System.out.println(kResourcesArsc + " in APK '" + path + "' is compressed.");
    }

    // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
    loaded_apk.resources_asset_ = loaded_apk.Open(kResourcesArsc, Asset.AccessMode.ACCESS_BUFFER);
    if (loaded_apk.resources_asset_ == null) {
      System.err.println("Failed to open '" + kResourcesArsc + "' in APK '" + path + "'.");
      return null;
    }

    // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
    loaded_apk.idmap_asset_ = idmap_asset;

  // const StringPiece data(
  //       reinterpret_cast<const char*>(loaded_apk.resources_asset_.getBuffer(true /*wordAligned*/)),
  //       loaded_apk.resources_asset_.getLength());
    StringPiece data = new StringPiece(
        ByteBuffer.wrap(loaded_apk.resources_asset_.getBuffer(true /*wordAligned*/))
            .order(ByteOrder.LITTLE_ENDIAN),
        0 /*(int) loaded_apk.resources_asset_.getLength()*/);
    loaded_apk.loaded_arsc_ =
        LoadedArsc.Load(data, loaded_idmap, system, load_as_shared_library);
    if (loaded_apk.loaded_arsc_ == null) {
      System.err.println("Failed to load '" + kResourcesArsc + "' in APK '" + path + "'.");
      return null;
    }

    // Need to force a move for mingw32.
    return loaded_apk;
  }

  private static String ErrorCodeString(int result) {
    return "Error " + result;
  }

  public Asset Open(String path, AccessMode mode) {
    CHECK(zip_handle_ != null);

    String name = path;
    ZipFileRO zipFileRO = ZipFileRO.open(zip_handle_.zipFile.getName());
    ZipEntryRO entry;
    entry = zipFileRO.findEntryByName(name);
    // int result = FindEntry(zip_handle_.get(), name, &entry);
    // if (result != 0) {
    //   LOG(ERROR) + "No entry '" + path + "' found in APK '" + path_ + "'";
    //   return {};
    // }
    if (entry == null) {
      return null;
    }

    if (entry.entry.getMethod() == kCompressDeflated) {
      // FileMap map = new FileMap();
      // if (!map.create(path_, .GetFileDescriptor(zip_handle_), entry.offset,
      //     entry.getCompressedSize(), true /*readOnly*/)) {
      //   LOG(ERROR) + "Failed to mmap file '" + path + "' in APK '" + path_ + "'";
      //   return {};
      // }
      FileMap map = zipFileRO.createEntryFileMap(entry);

      Asset asset =
          Asset.createFromCompressedMap(map, (int) entry.entry.getSize(), mode);
      if (asset == null) {
        System.err.println("Failed to decompress '" + path + "'.");
        return null;
      }
      return asset;
    } else {
      FileMap map = zipFileRO.createEntryFileMap(entry);

      // if (!map.create(path_, .GetFileDescriptor(zip_handle_.get()), entry.offset,
      //     entry.uncompressed_length, true /*readOnly*/)) {
      //   System.err.println("Failed to mmap file '" + path + "' in APK '" + path_ + "'");
      //   return null;
      // }

      Asset asset = Asset.createFromUncompressedMap(map, mode);
      if (asset == null) {
        System.err.println("Failed to mmap file '" + path + "' in APK '" + path_ + "'");
        return null;
      }
      return asset;
    }
  }

  interface ForEachFileCallback {
    void callback(String string, FileType fileType);
  }

  boolean ForEachFile(String root_path,
      ForEachFileCallback f) {
    CHECK(zip_handle_ != null);

    String root_path_full = root_path;
    // if (root_path_full.back() != '/') {
    if (!root_path_full.endsWith("/")) {
      root_path_full += '/';
    }

    String prefix = root_path_full;
    Enumeration<? extends ZipEntry> entries = zip_handle_.zipFile.entries();
    // if (StartIteration(zip_handle_.get(), &cookie, &prefix, null) != 0) {
    //   return false;
    // }
    if (!entries.hasMoreElements()) {
      return false;
    }

    // String name;
    // ZipEntry entry;

    // We need to hold back directories because many paths will contain them and we want to only
    // surface one.
    final Set<String> dirs = new HashSet<>();

    // int32_t result;
    // while ((result = Next(cookie, &entry, &name)) == 0) {
    while (entries.hasMoreElements()) {
      ZipEntry zipEntry =  entries.nextElement();
      if (!zipEntry.getName().startsWith(prefix)) {
        continue;
      }

      // StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length);
      String full_file_path = zipEntry.getName();

      // StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
      String leaf_file_path = full_file_path.substring(root_path_full.length());

      if (!leaf_file_path.isEmpty()) {
        // auto iter = stdfind(leaf_file_path.begin(), leaf_file_path.end(), '/');

        // if (iter != leaf_file_path.end()) {
        //   stdstring dir =
        //       leaf_file_path.substr(0, stddistance(leaf_file_path.begin(), iter)).to_string();
        //   dirs.insert(stdmove(dir));
        if (zipEntry.isDirectory()) {
          dirs.add(leaf_file_path.substring(0, leaf_file_path.indexOf("/")));
        } else {
          f.callback(leaf_file_path, kFileTypeRegular);
        }
      }
    }
    // EndIteration(cookie);

    // Now present the unique directories.
    for (final String dir : dirs) {
      f.callback(dir, kFileTypeDirectory);
    }

    // -1 is end of iteration, anything else is an error.
    // return result == -1;
    return true;
  }
//
}  // namespace android


package org.robolectric.internal;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.robolectric.annotation.Config;
import org.robolectric.res.Fs;

/**
 * @deprecated This method of configuration will be removed in a forthcoming release. Build systems
 *     should follow http://robolectric.org/build-system-integration/ to provide integration with
 *     Robolectric.
 */
@Deprecated
@SuppressWarnings("NewApi")
public class MavenManifestFactory implements ManifestFactory {

  @Override
  public ManifestIdentifier identify(Config config) {
    final String manifestPath = config.manifest();
    if (manifestPath.equals(Config.NONE)) {
      return new ManifestIdentifier((String) null, null, null, null, null);
    }

    // Try to locate the manifest file as a classpath resource; fallback to using the base dir.
    final Path manifestFile;
    final String resourceName = manifestPath.startsWith("/") ? manifestPath : ("/" + manifestPath);
    final URL resourceUrl = getClass().getResource(resourceName);
    if (resourceUrl != null && "file".equals(resourceUrl.getProtocol())) {
      // Construct a path to the manifest file relative to the current working directory.
      final Path workingDirectory = Paths.get(System.getProperty("user.dir"));
      final Path absolutePath = Fs.fromUrl(resourceUrl);
      manifestFile = workingDirectory.relativize(absolutePath);
    } else {
      manifestFile = getBaseDir().resolve(manifestPath);
    }

    final Path baseDir = manifestFile.getParent();
    final Path resDir = baseDir.resolve(config.resourceDir());
    final Path assetDir = baseDir.resolve(config.assetDir());

    List<ManifestIdentifier> libraries;
    if (config.libraries().length == 0) {
      // If there is no library override, look through subdirectories.
      try {
        libraries = findLibraries(resDir);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    } else {
      libraries = new ArrayList<>();
      for (String libraryDirName : config.libraries()) {
        Path libDir = baseDir.resolve(libraryDirName);
        libraries.add(
            new ManifestIdentifier(
                null,
                libDir.resolve(Config.DEFAULT_MANIFEST_NAME),
                libDir.resolve(Config.DEFAULT_RES_FOLDER),
                libDir.resolve(Config.DEFAULT_ASSET_FOLDER),
                null));
      }
    }

    return new ManifestIdentifier(config.packageName(), manifestFile, resDir, assetDir, libraries);
  }

  Path getBaseDir() {
    return Paths.get(".");
  }

  private static Properties getProperties(Path propertiesFile) {
    Properties properties = new Properties();

    // return an empty Properties object if the propertiesFile does not exist
    if (!Files.exists(propertiesFile)) return properties;

    InputStream stream;
    try {
      stream = Fs.getInputStream(propertiesFile);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    try {
      try {
        properties.load(stream);
      } finally {
        stream.close();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return properties;
  }

  /**
   * Find valid library AndroidManifest files referenced from an already loaded AndroidManifest's
   * `project.properties` file, recursively.
   */
  private static List<ManifestIdentifier> findLibraries(Path resDirectory) throws IOException {
    List<ManifestIdentifier> libraryBaseDirs = new ArrayList<>();

    if (resDirectory != null) {
      Path baseDir = resDirectory.getParent();
      final Properties properties = getProperties(baseDir.resolve("project.properties"));
      Properties overrideProperties = getProperties(baseDir.resolve("test-project.properties"));
      properties.putAll(overrideProperties);

      int libRef = 1;
      String lib;
      while ((lib = properties.getProperty("android.library.reference." + libRef)) != null) {
        Path libraryDir = baseDir.resolve(lib);
        if (Files.isDirectory(libraryDir)) {
          // Ignore directories without any files
          Path[] libraryBaseDirFiles = Fs.listFiles(libraryDir);
          if (libraryBaseDirFiles != null && libraryBaseDirFiles.length > 0) {
            List<ManifestIdentifier> libraries =
                findLibraries(libraryDir.resolve(Config.DEFAULT_RES_FOLDER));
            libraryBaseDirs.add(
                new ManifestIdentifier(
                    null,
                    libraryDir.resolve(Config.DEFAULT_MANIFEST_NAME),
                    libraryDir.resolve(Config.DEFAULT_RES_FOLDER),
                    libraryDir.resolve(Config.DEFAULT_ASSET_FOLDER),
                    libraries));
          }
        }

        libRef++;
      }
    }
    return libraryBaseDirs;
  }
}