/** Launch a client app for the given concert app. */
  public static Result launch(
      final Activity parent,
      final RoconDescription concert,
      final rocon_interaction_msgs.Interaction app) {

    Log.i(
        "AppLaunch",
        "launching concert app " + app.getDisplayName() + " on service " + app.getNamespace());

    // On android apps, app name will be an intent action, while for web apps it will be its URL
    if (Patterns.WEB_URL.matcher(app.getName()).matches() == true) {
      return launchWebApp(parent, concert, app);
    } else if (app.getName().length() == 0) {
      return Result.NOTHING;
    } else if (checkAppName(app.getName()).length() != 0) {
      return launchWebApp(parent, concert, app);
    } else {
      return launchAndroidApp(parent, concert, app);
    }
  }
  /** Launch a client web app for the given concert app. */
  private static Result launchWebApp(
      final Activity parent,
      final RoconDescription concert,
      final rocon_interaction_msgs.Interaction app) {
    try {
      // Validate the URL before starting anything
      String app_name = "";
      String app_type = "";

      // Parse the url
      if (checkAppType(app.getName()) == AppType.WEB_URL) {
        app_type = "web_url";
        app_name = app.getName().substring(app_type.length() + 1, app.getName().length() - 1);
      } else if (checkAppType(app.getName()) == AppType.WEB_APP) {
        app_type = "web_app";
        app_name = app.getName().substring(app_type.length() + 1, app.getName().length() - 1);
      } else {
        app_name = app.getName();
      }

      URL appURL = new URL(app_name);

      AsyncTask<URL, Void, String> asyncTask =
          new AsyncTask<URL, Void, String>() {
            @Override
            protected String doInBackground(URL... urls) {
              try {
                HttpURLConnection urlConnection = (HttpURLConnection) urls[0].openConnection();
                int unused_responseCode = urlConnection.getResponseCode();
                urlConnection.disconnect();
                return urlConnection.getResponseMessage();
              } catch (IOException e) {
                return e.getMessage();
              }
            }
          }.execute(appURL);
      String result = asyncTask.get(5, TimeUnit.SECONDS);
      if (result == null
          || (result.startsWith("OK") == false && result.startsWith("ok") == false)) {
        return Result.CANNOT_CONNECT.withMsg(result);
      }

      // We pass concert URL, parameters and remaps as URL parameters
      String appUriStr = app_name;
      String interaction_data = "{";
      // add remap
      String remaps = "\"remappings\": {";
      if ((app.getRemappings() != null) && (app.getRemappings().size() > 0)) {
        for (rocon_std_msgs.Remapping remap : app.getRemappings())
          remaps += "\"" + remap.getRemapFrom() + "\":\"" + remap.getRemapTo() + "\",";
        remaps = remaps.substring(0, remaps.length() - 1) + "}";
      } else {
        remaps += "}";
      }
      remaps += ",";
      interaction_data += remaps;

      // add displayname
      String displayname = "\"display_name\":";
      if ((app.getDisplayName() != null) && (app.getDisplayName().length() > 0)) {
        displayname += "\"" + app.getDisplayName() + "\"";
      }
      displayname += ",";
      interaction_data += displayname;

      // add parameters
      String parameters = "\"parameters\": {";
      if ((app.getParameters() != null) && (app.getParameters().length() > 0)) {
        Yaml yaml = new Yaml();
        Map<String, String> params = (Map<String, String>) yaml.load(app.getParameters());
        for (String key : params.keySet()) {
          parameters += "\"" + key + "\":\"" + String.valueOf(params.get(key)) + "" + "\",";
        }
        parameters = parameters.substring(0, parameters.length() - 1);
      }

      parameters += "}";
      interaction_data += parameters;
      interaction_data += "}";

      if (app_type.equals("web_url")) {
        appUriStr = appUriStr;
      } else if (app_type.equals("web_app")) {
        appUriStr = appUriStr + "?" + "interaction_data=" + URLEncoder.encode(interaction_data);
      } else {
        appUriStr = appUriStr + "?" + "interaction_data=" + URLEncoder.encode(interaction_data);
      }
      appURL.toURI(); // throws URISyntaxException if fails; probably a redundant check
      Uri appURI = Uri.parse(appUriStr);

      // Create an action view intent and pass rapp's name + extra information as URI
      Intent intent = new Intent(Intent.ACTION_VIEW, appURI);

      Log.i("AppLaunch", "trying to start web app (URI: " + appUriStr + ")");
      parent.startActivity(intent);
      return Result.SUCCESS;
    } catch (URISyntaxException e) {
      return Result.MALFORMED_URI.withMsg("Cannot convert URL into URI. " + e.getMessage());
    } catch (MalformedURLException e) {
      return Result.MALFORMED_URI.withMsg("App URL is not valid. " + e.getMessage());
    } catch (ActivityNotFoundException e) {
      // This cannot happen for a web site, right? must mean that I have no web browser!
      return Result.NOT_INSTALLED.withMsg("Activity not found for view action??? muoia???");
    } catch (TimeoutException e) {
      return Result.CONNECT_TIMEOUT.withMsg("Timeout waiting for app");
    } catch (Exception e) {
      return Result.OTHER_ERROR.withMsg(e.getMessage());
    }
  }