/**
   * Installs the given platform and its dependencies into the given installation, if necessary.
   *
   * @param logger Logs things.
   * @param launcher Used to launch tasks on the remote node.
   * @param sdk SDK installation to install components for.
   * @param platform Specifies the platform to be installed.
   */
  public static void installPlatform(
      PrintStream logger, Launcher launcher, AndroidSdk sdk, String platform)
      throws IOException, InterruptedException {
    // Check whether this platform is already installed
    if (isPlatformInstalled(logger, launcher, sdk, platform)) {
      return;
    }

    // Check whether we are capable of installing individual components
    log(logger, Messages.PLATFORM_INSTALL_REQUIRED(platform));
    if (!launcher.isUnix() && platform.contains(":") && sdk.getSdkToolsVersion() < 16) {
      // SDK add-ons can't be installed on Windows until r16 due to http://b.android.com/18868
      log(logger, Messages.SDK_ADDON_INSTALLATION_UNSUPPORTED());
      return;
    }
    if (!sdk.supportsComponentInstallation()) {
      log(logger, Messages.SDK_COMPONENT_INSTALLATION_UNSUPPORTED());
      return;
    }

    // Automated installation of ABIs (required for android-14+) is not possible until r17, so
    // we should warn the user that we can't automatically set up an AVD with older SDK Tools.
    // See http://b.android.com/21880
    if ((platform.endsWith("14") || platform.endsWith("15"))
        && !sdk.supportsSystemImageInstallation()) {
      log(logger, Messages.ABI_INSTALLATION_UNSUPPORTED(), true);
    }

    // Determine which individual component(s) need to be installed for this platform
    List<String> components = getSdkComponentsForPlatform(logger, platform);
    if (components == null || components.size() == 0) {
      return;
    }

    // If a platform expanded to multiple dependencies (e.g. "GoogleMaps:7" -> android-7 + Maps)
    // then check whether we really need to install android-7, as it may already be installed
    if (components.size() > 1) {
      for (Iterator<String> it = components.iterator(); it.hasNext(); ) {
        String component = it.next();
        if (isPlatformInstalled(logger, launcher, sdk, component)) {
          it.remove();
        }
      }
    }

    // Grab the lock and attempt installation
    Semaphore semaphore = acquireLock();
    try {
      installComponent(logger, launcher, sdk, components.toArray(new String[0]));
    } finally {
      semaphore.release();
    }
  }
  private static AndroidSdk doInstall(
      Launcher launcher, BuildListener listener, String androidSdkHome)
      throws SdkInstallationException, IOException, InterruptedException {
    // We should install the SDK on the current build machine
    Node node = Computer.currentComputer().getNode();

    // Install the SDK if required
    String androidHome;
    try {
      androidHome = installBasicSdk(listener, node).getRemote();
    } catch (IOException e) {
      throw new SdkInstallationException(Messages.SDK_DOWNLOAD_FAILED(), e);
    } catch (SdkUnavailableException e) {
      throw new SdkInstallationException(Messages.SDK_DOWNLOAD_FAILED(), e);
    }

    // Check whether we need to install the SDK components
    if (!isSdkInstallComplete(node, androidHome)) {
      PrintStream logger = listener.getLogger();
      log(logger, Messages.INSTALLING_REQUIRED_COMPONENTS());
      AndroidSdk sdk = new AndroidSdk(androidHome, androidSdkHome);
      installComponent(logger, launcher, sdk, "platform-tool", "tool");

      // If we made it this far, confirm completion by writing our our metadata file
      getInstallationInfoFilename(node).write(String.valueOf(SDK_VERSION), "UTF-8");

      // As this SDK will not be used manually, opt out of the stats gathering;
      // this also prevents the opt-in dialog from popping up during execution
      optOutOfSdkStatistics(launcher, listener, androidSdkHome);
    }

    // Create an SDK object now that all the components exist
    return Utils.getAndroidSdk(launcher, androidHome, androidSdkHome);
  }
 /**
  * Writes the configuration file required to opt out of SDK usage statistics gathering.
  *
  * @param launcher Used for running tasks on the remote node.
  * @param listener Used to access logger.
  */
 public static void optOutOfSdkStatistics(
     Launcher launcher, BuildListener listener, String androidSdkHome) {
   Callable<Void, Exception> optOutTask = new StatsOptOutTask(androidSdkHome, listener);
   try {
     launcher.getChannel().call(optOutTask);
   } catch (Exception e) {
     log(listener.getLogger(), "SDK statistics opt-out failed.", e);
   }
 }
  /**
   * Installs the given SDK component(s) into the given installation.
   *
   * @param logger Logs things.
   * @param launcher Used to launch tasks on the remote node.
   * @param sdk Root of the SDK installation to install components for.
   * @param components Name of the component(s) to install.
   */
  private static void installComponent(
      PrintStream logger, Launcher launcher, AndroidSdk sdk, String... components)
      throws IOException, InterruptedException {
    String proxySettings = getProxySettings();

    String list = StringUtils.join(components, ',');
    log(logger, Messages.INSTALLING_SDK_COMPONENTS(list.toString()));
    String all = sdk.getSdkToolsVersion() < 17 ? "-o" : "-a";
    String upgradeArgs = String.format("update sdk -u %s %s -t %s", all, proxySettings, list);

    Utils.runAndroidTool(launcher, logger, logger, sdk, Tool.ANDROID, upgradeArgs, null);
  }
  private static List<String> getSdkComponentsForPlatform(PrintStream logger, String platform) {
    // Gather list of required components
    List<String> components = new ArrayList<String>();

    // Check for dependent platform
    int dependentPlatform = -1;
    Matcher matcher = Pattern.compile("[0-9]{1,2}$").matcher(platform);
    if (matcher.find()) {
      String end = matcher.group();
      try {
        dependentPlatform = Integer.parseInt(end);
      } catch (NumberFormatException e) {
      }
    }

    // Add dependent platform
    if (dependentPlatform > 0) {
      components.add(String.format("android-%s", dependentPlatform));
    }

    // Add system image, if required
    // Even if a system image doesn't exist for this platform, the installer silently ignores it
    if (dependentPlatform >= 14) {
      components.add(String.format("sysimg-%s", dependentPlatform));
    }

    // If it's a straightforward case like "android-10", we're done
    if (!platform.contains(":")) {
      return components;
    }

    // As of SDK r17-ish, we can't always map addon names directly to installable components.
    // But replacing display name "Google Inc." with vendor ID "google" should cover most cases
    platform = platform.replace("Google Inc.", "google");

    String parts[] = platform.toLowerCase().split(":");
    if (parts.length != 3) {
      log(logger, Messages.SDK_ADDON_FORMAT_UNRECOGNISED(platform));
      return null;
    }

    // Determine addon name
    String vendor =
        parts[0].replaceAll("[^a-z0-9_-]+", "_").replaceAll("_+", "_").replace("_$", "");
    String addon = parts[1].replaceAll("[^a-z0-9_-]+", "_").replaceAll("_+", "_").replace("_$", "");
    String component = String.format("addon-%s-%s-%s", addon, vendor, parts[2]);
    components.add(component);

    return components;
  }
    public Void call() throws Exception {
      if (logger == null) {
        logger = listener.getLogger();
      }

      final File homeDir = Utils.getHomeDirectory(androidSdkHome);
      final File androidDir = new File(homeDir, ".android");
      androidDir.mkdirs();

      File configFile = new File(androidDir, "ddms.cfg");
      PrintWriter out;
      try {
        out = new PrintWriter(configFile);
        out.println("pingOptIn=false");
        out.flush();
        out.close();
      } catch (FileNotFoundException e) {
        log(logger, "Failed to automatically opt out of SDK statistics gathering.", e);
      }

      return null;
    }
  /**
   * Downloads and extracts the basic Android SDK on a given Node, if it hasn't already been done.
   *
   * @param node Node to install the SDK on.
   * @return Path where the SDK is installed, regardless of whether it was installed right now.
   * @throws SdkUnavailableException If the Android SDK is not available on this platform.
   */
  private static FilePath installBasicSdk(final BuildListener listener, Node node)
      throws SdkUnavailableException, IOException, InterruptedException {
    // Locate where the SDK should be installed to on this node
    final FilePath installDir = Utils.getSdkInstallDirectory(node);

    // Get the OS-specific download URL for the SDK
    AndroidInstaller installer = AndroidInstaller.fromNode(node);
    final URL downloadUrl = installer.getUrl(SDK_VERSION);

    // Download the SDK, if required
    boolean wasNowInstalled =
        installDir.act(
            new FileCallable<Boolean>() {
              public Boolean invoke(File f, VirtualChannel channel)
                  throws InterruptedException, IOException {
                String msg = Messages.DOWNLOADING_SDK_FROM(downloadUrl);
                return installDir.installIfNecessaryFrom(downloadUrl, listener, msg);
              }

              private static final long serialVersionUID = 1L;
            });

    if (wasNowInstalled) {
      // If the SDK was required, pull files up from the intermediate directory
      installDir.listDirectories().get(0).moveAllChildrenTo(installDir);

      // Java's ZipEntry doesn't preserve the executable bit...
      if (installer == AndroidInstaller.MAC_OS_X) {
        setPermissions(installDir.child("tools"));
      }

      // Success!
      log(listener.getLogger(), Messages.BASE_SDK_INSTALLED());
    }

    return installDir;
  }