@NonNull
  public static synchronized RomInformation[] getRoms(Context context) {
    RomInformation[] roms = new RomInformation[0];

    try {
      roms = MbtoolSocket.getInstance().getInstalledRoms(context);

      for (RomInformation rom : roms) {
        rom.setThumbnailPath(
            Environment.getExternalStorageDirectory()
                + "/MultiBoot/"
                + rom.getId()
                + "/thumbnail.webp");
        rom.setWallpaperPath(
            Environment.getExternalStorageDirectory()
                + "/MultiBoot/"
                + rom.getId()
                + "/wallpaper.webp");
        rom.setConfigPath(
            Environment.getExternalStorageDirectory()
                + "/MultiBoot/"
                + rom.getId()
                + "/config.json");
        rom.setImageResId(R.drawable.rom_android);
        rom.setDefaultName(getDefaultName(context, rom));

        loadConfig(rom);
      }
    } catch (IOException e) {
      Log.e(TAG, "mbtool communication error", e);
    }

    return roms;
  }
  private static boolean usesLiveWallpaper(Context context, RomInformation info) {
    String wallpaperInfoPath = info.getDataPath() + "/system/users/0/wallpaper_info.xml";

    MbtoolSocket socket = MbtoolSocket.getInstance();
    int id = -1;

    try {
      id = socket.fileOpen(context, wallpaperInfoPath, new short[] {FileOpenFlag.RDONLY}, 0);
      if (id < 0) {
        return false;
      }

      StatBuf sb = socket.fileStat(context, id);
      if (sb == null) {
        return false;
      }

      // Check file size
      if (sb.st_size < 0 || sb.st_size > 1024) {
        return false;
      }

      // Read file into memory
      byte[] data = new byte[(int) sb.st_size];
      int nWritten = 0;
      while (nWritten < data.length) {
        ByteBuffer newData = socket.fileRead(context, id, 10240);
        if (newData == null) {
          return false;
        }

        int nRead = newData.limit() - newData.position();
        newData.get(data, nWritten, nRead);
        nWritten += nRead;
      }

      socket.fileClose(context, id);
      id = -1;

      String xml = new String(data, Charsets.UTF_8);
      return xml.contains("component=");
    } catch (IOException e) {
      return false;
    } finally {
      if (id >= 0) {
        try {
          socket.fileClose(context, id);
        } catch (IOException e) {
          // Ignore
        }
      }
    }
  }
  @Nullable
  public static RomInformation getCurrentRom(Context context) {
    try {
      String id = MbtoolSocket.getInstance().getBootedRomId(context);
      Log.d(TAG, "mbtool says current ROM ID is: " + id);

      for (RomInformation rom : getRoms(context)) {
        if (rom.getId().equals(id)) {
          return rom;
        }
      }
    } catch (IOException e) {
      Log.e(TAG, "mbtool communication error", e);
    }

    return null;
  }
  public static CacheWallpaperResult cacheWallpaper(Context context, RomInformation info) {
    if (usesLiveWallpaper(context, info)) {
      // We can't render a snapshot of a live wallpaper
      return CacheWallpaperResult.USES_LIVE_WALLPAPER;
    }

    String wallpaperPath = info.getDataPath() + "/system/users/0/wallpaper";
    File wallpaperCacheFile = new File(info.getWallpaperPath());
    FileOutputStream fos = null;

    MbtoolSocket socket = MbtoolSocket.getInstance();
    int id = -1;

    try {
      id = socket.fileOpen(context, wallpaperPath, new short[] {}, 0);
      if (id < 0) {
        return CacheWallpaperResult.FAILED;
      }

      // Check if we need to re-cache the file
      StatBuf sb = socket.fileStat(context, id);
      if (sb == null) {
        return CacheWallpaperResult.FAILED;
      }

      if (wallpaperCacheFile.exists() && wallpaperCacheFile.lastModified() / 1000 > sb.st_mtime) {
        Log.d(TAG, "Wallpaper for " + info.getId() + " has not been changed");
        return CacheWallpaperResult.UP_TO_DATE;
      }

      // Ignore large wallpapers
      if (sb.st_size < 0 || sb.st_size > 20 * 1024 * 1024) {
        return CacheWallpaperResult.FAILED;
      }

      // Read file into memory
      byte[] data = new byte[(int) sb.st_size];
      int nWritten = 0;
      while (nWritten < data.length) {
        ByteBuffer newData = socket.fileRead(context, id, 10240);
        if (newData == null) {
          return CacheWallpaperResult.FAILED;
        }

        int nRead = newData.limit() - newData.position();
        newData.get(data, nWritten, nRead);
        nWritten += nRead;
      }

      socket.fileClose(context, id);
      id = -1;

      fos = new FileOutputStream(wallpaperCacheFile);

      // Compression can be very slow (more than 10 seconds) for a large wallpaper, so we'll
      // just cache the actual file instead
      fos.write(data);

      // Load into bitmap
      // Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
      // if (bitmap == null) {
      //    return false;
      // }
      // bitmap.compress(Bitmap.CompressFormat.WEBP, 100, fos);
      // bitmap.recycle();

      // Invalidate picasso cache
      Picasso.with(context).invalidate(wallpaperCacheFile);

      Log.d(TAG, "Wallpaper for " + info.getId() + " has been cached");
      return CacheWallpaperResult.UPDATED;
    } catch (IOException e) {
      Log.e(TAG, "Failed to cache wallpaper for " + info.getId(), e);
      return CacheWallpaperResult.FAILED;
    } finally {
      if (id >= 0) {
        try {
          socket.fileClose(context, id);
        } catch (IOException e) {
          // Ignore
        }
      }
      IOUtils.closeQuietly(fos);
    }
  }