private static Map<String, ExchangeRate> requestExchangeRates(
      final URL url, final String... fields) {
    final long start = System.currentTimeMillis();

    HttpURLConnection connection = null;
    Reader reader = null;

    try {
      connection = (HttpURLConnection) url.openConnection();
      connection.setConnectTimeout(Constants.HTTP_TIMEOUT_MS);
      connection.setReadTimeout(Constants.HTTP_TIMEOUT_MS);
      connection.connect();

      final int responseCode = connection.getResponseCode();
      if (responseCode == HttpURLConnection.HTTP_OK) {
        reader =
            new InputStreamReader(
                new BufferedInputStream(connection.getInputStream(), 1024), Constants.UTF_8);
        final StringBuilder content = new StringBuilder();
        Io.copy(reader, content);

        final Map<String, ExchangeRate> rates = new TreeMap<String, ExchangeRate>();

        final JSONObject head = new JSONObject(content.toString());
        for (final Iterator<String> i = head.keys(); i.hasNext(); ) {
          final String currencyCode = i.next();
          if (!"timestamp".equals(currencyCode)) {
            final JSONObject o = head.getJSONObject(currencyCode);

            for (final String field : fields) {
              final String rateStr = o.optString(field, null);

              if (rateStr != null) {
                try {
                  final BigInteger rate = GenericUtils.toNanoCoins(rateStr, 0);

                  if (rate.signum() > 0) {
                    rates.put(currencyCode, new ExchangeRate(currencyCode, rate, url.getHost()));
                    break;
                  }
                } catch (final ArithmeticException x) {
                  log.warn("problem fetching exchange rate: " + currencyCode, x);
                }
              }
            }
          }
        }

        log.info(
            "fetched exchange rates from "
                + url
                + ", took "
                + (System.currentTimeMillis() - start)
                + " ms");

        return rates;
      } else {
        log.warn("http status " + responseCode + " when fetching " + url);
      }
    } catch (final Exception x) {
      log.warn("problem fetching exchange rates", x);
    } finally {
      if (reader != null) {
        try {
          reader.close();
        } catch (final IOException x) {
          // swallow
        }
      }

      if (connection != null) connection.disconnect();
    }

    return null;
  }
  @Override
  public void onClick(final DialogInterface dialog, final int which) {
    final StringBuilder text = new StringBuilder();
    final ArrayList<Uri> attachments = new ArrayList<Uri>();
    final File cacheDir = context.getCacheDir();

    text.append(viewDescription.getText()).append('\n');

    try {
      text.append("\n\n\n=== application info ===\n\n");

      final CharSequence applicationInfo = collectApplicationInfo();

      text.append(applicationInfo);
    } catch (final IOException x) {
      text.append(x.toString()).append('\n');
    }

    try {
      final CharSequence stackTrace = collectStackTrace();

      if (stackTrace != null) {
        text.append("\n\n\n=== stack trace ===\n\n");
        text.append(stackTrace);
      }
    } catch (final IOException x) {
      text.append("\n\n\n=== stack trace ===\n\n");
      text.append(x.toString()).append('\n');
    }

    if (viewCollectDeviceInfo.isChecked()) {
      try {
        text.append("\n\n\n=== device info ===\n\n");

        final CharSequence deviceInfo = collectDeviceInfo();

        text.append(deviceInfo);
      } catch (final IOException x) {
        text.append(x.toString()).append('\n');
      }
    }

    if (viewCollectInstalledPackages.isChecked()) {
      try {
        text.append("\n\n\n=== installed packages ===\n\n");
        CrashReporter.appendInstalledPackages(text, context);
      } catch (final IOException x) {
        text.append(x.toString()).append('\n');
      }
    }

    if (viewCollectApplicationLog.isChecked()) {
      try {
        final File logDir = context.getDir("log", Context.MODE_PRIVATE);

        for (final File logFile : logDir.listFiles()) {
          final String logFileName = logFile.getName();
          final File file;
          if (logFileName.endsWith(".log.gz"))
            file =
                File.createTempFile(
                    logFileName.substring(0, logFileName.length() - 6), ".log.gz", cacheDir);
          else if (logFileName.endsWith(".log"))
            file =
                File.createTempFile(
                    logFileName.substring(0, logFileName.length() - 3), ".log", cacheDir);
          else continue;

          final InputStream is = new FileInputStream(logFile);
          final OutputStream os = new FileOutputStream(file);

          Io.copy(is, os);

          os.close();
          is.close();

          Io.chmod(file, 0777);

          attachments.add(Uri.fromFile(file));
        }
      } catch (final IOException x) {
        log.info("problem writing attachment", x);
      }
    }

    if (viewCollectWalletDump.isChecked()) {
      try {
        final CharSequence walletDump = collectWalletDump();

        if (walletDump != null) {
          final File file = File.createTempFile("wallet-dump.", ".txt", cacheDir);

          final Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8);
          writer.write(walletDump.toString());
          writer.close();

          Io.chmod(file, 0777);

          attachments.add(Uri.fromFile(file));
        }
      } catch (final IOException x) {
        log.info("problem writing attachment", x);
      }
    }

    if (CrashReporter.hasSavedBackgroundTraces()) {
      text.append("\n\n\n=== saved exceptions ===\n\n");

      try {
        CrashReporter.appendSavedBackgroundTraces(text);
      } catch (final IOException x) {
        text.append(x.toString()).append('\n');
      }
    }

    text.append("\n\nPUT ADDITIONAL COMMENTS TO THE TOP. DOWN HERE NOBODY WILL NOTICE.");

    startSend(subject(), text, attachments);
  }