////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    protected override bool ValidateParameters ()
    {
      // 
      // This tool should really only expect 'AndroidManifest.xml' input. But people might change the names of these, so we can't rely on that to validate.
      // 

      bool validParameters = true;

      if (Sources.Length != 1)
      {
        Log.LogError ("Expected single input, got: " + Sources.Length + "'.");

        validParameters = false;
      }

      if (validParameters)
      {
        string sourcePath = Path.GetFullPath (Sources [0].GetMetadata ("FullPath"));

        if (string.IsNullOrEmpty (sourcePath) || !File.Exists (sourcePath))
        {
          Log.LogError ("Could locate expected input: '" + sourcePath + "'.");

          validParameters = false;
        }

        try
        {
          if (validParameters)
          {
            AndroidManifestDocument sourceManifest = new AndroidManifestDocument ();

            sourceManifest.Load (sourcePath);
          }
        }
        catch (Exception e)
        {
          Log.LogError ("Could not successfully parse '" + sourcePath + "'. Check this is a valid AndroidManifest.xml document. Reason: " + e, MessageImportance.High);

          Log.LogErrorFromException (e, true);

          validParameters = false;
        }
      }

      return (validParameters && base.ValidateParameters ());
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    protected override int TrackedExecuteTool (string pathToTool, string responseFileCommands, string commandLineCommands)
    {
      // 
      // Use default implementation, but ensure that any generated sources and dependency files get flagged as output appropriately.
      // 

      int retCode = -1;

      Dictionary<string, ITaskItem> processedManifestFiles = new Dictionary<string, ITaskItem> ();

      List<ITaskItem> outputApkFileList = new List<ITaskItem> ();

      List<ITaskItem> outputFilesList = new List<ITaskItem> ();

      try
      {
        foreach (ITaskItem source in Sources)
        {
          string sourcePath = Path.GetFullPath (source.GetMetadata ("FullPath"));

          if (!processedManifestFiles.ContainsKey (sourcePath))
          {
            retCode = base.TrackedExecuteTool (pathToTool, responseFileCommands, commandLineCommands);

            processedManifestFiles.Add (sourcePath, source);

            if (retCode == 0)
            {
              AndroidManifestDocument sourceManifest = new AndroidManifestDocument ();

              sourceManifest.Load (sourcePath);

              // 
              // Determine if this manifest requested APK generation, add to output list.
              // 

              if (!string.IsNullOrWhiteSpace (source.GetMetadata ("ApkOutputFile")))
              {
                string apkOutputFile = source.GetMetadata ("ApkOutputFile");

                if (!File.Exists (apkOutputFile))
                {
                  throw new FileNotFoundException ("Requested APK output file does not exist. Error during packaging?");
                }

                ITaskItem outputApkItem = new TaskItem (Path.GetFullPath (apkOutputFile));

                outputApkFileList.Add (outputApkItem);

                outputFilesList.Add (outputApkItem);
              }


              if (source.GetMetadata ("GenerateDependencies") == "true")
              {
                // 
                // Evaluate which resource constant source files have been exported. 'ExtraPackages' lists additional package names that also have resource ids.
                // 

                List<string> resourceConstantSourceFiles = new List<string> ();

                string resourcesDirectory = source.GetMetadata ("ResourceConstantsOutputDirectory");

                if (File.Exists (Path.Combine (resourcesDirectory, "R.java")))
                {
                  resourceConstantSourceFiles.Add (Path.Combine (resourcesDirectory, "R.java"));
                }

                if (File.Exists (Path.Combine (resourcesDirectory, sourceManifest.PackageName.Replace ('.', '\\'), "R.java")))
                {
                  resourceConstantSourceFiles.Add (Path.Combine (resourcesDirectory, sourceManifest.PackageName.Replace ('.', '\\'), "R.java"));
                }

                if (!string.IsNullOrWhiteSpace (source.GetMetadata ("ExtraPackages")))
                {
                  string [] extraPackages = source.GetMetadata ("ExtraPackages").Split (':');

                  foreach (string package in extraPackages)
                  {
                    if (File.Exists (Path.Combine (resourcesDirectory, package.Replace ('.', '\\'), "R.java")))
                    {
                      resourceConstantSourceFiles.Add (Path.Combine (resourcesDirectory, package.Replace ('.', '\\'), "R.java"));
                    }
                  }
                }

                // 
                // When exporting an APK the R.java.d file will not properly list dependencies. Copy from ??.apk.d to solve this.
                // 

                string dependencyFileSource = Path.Combine (resourcesDirectory, "R.java.d");

                if (!string.IsNullOrWhiteSpace (source.GetMetadata ("ApkOutputFile")))
                {
                  string apkDependencyFileSource = source.GetMetadata ("ApkOutputFile") + ".d";

                  GccUtilities.DependencyParser.ConvertJavaDependencyFileToGcc (apkDependencyFileSource);

                  File.Copy (apkDependencyFileSource, dependencyFileSource, true); // overwrite invalid R.java.d export
                }

                // 
                // R.java.d dependency files are not placed alongside its master file if exported using package directories. Fix this.
                // 

                foreach (string master in resourceConstantSourceFiles)
                {
                  string masterDependencyFile = master + ".d";

                  Directory.CreateDirectory (Path.GetDirectoryName (masterDependencyFile));

                  if (File.Exists (masterDependencyFile))
                  {
                    File.Delete (masterDependencyFile);
                  }

                  File.Copy (dependencyFileSource, masterDependencyFile);
                }

                // 
                // Finally ensure all resource constant export files are added to the output list.
                // 

                foreach (string file in resourceConstantSourceFiles)
                {
                  ITaskItem outputResourceFileItem = new TaskItem (Path.GetFullPath (file));

                  outputFilesList.Add (outputResourceFileItem);
                }
              }

              if (retCode != 0)
              {
                break;
              }
            }
          }
        }
      }
      catch (Exception e)
      {
        Log.LogErrorFromException (e, true);

        retCode = -1;
      }
      finally
      {
        OutputApk = outputApkFileList.ToArray ();

        OutputFiles = outputFilesList.ToArray ();
      }

      return retCode;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public override bool Execute ()
    {
      try
      {
        if (PrimaryManifest.Length == 0)
        {
          Log.LogError ("No 'PrimaryManifest' file(s) specified.");

          return false;
        }

        if (PrimaryManifest.Length > 1)
        {
          Log.LogError ("Too many 'PrimaryManifest' files specified. Expected one.");

          return false;
        }

        if (ProjectManifests.Length == 0)
        {
          Log.LogError ("No 'ProjectManifests' file(s) specified.");

          return false;
        }

        // 
        // Identify which is the primary AndroidManifest.xml provided.
        // 

        MergedManifest = null;

        string primaryManifestFullPath = PrimaryManifest [0].GetMetadata ("FullPath");

        foreach (ITaskItem manifestItem in ProjectManifests)
        {
          string manifestItemFullPath = manifestItem.GetMetadata ("FullPath");

          if (primaryManifestFullPath.Equals (manifestItemFullPath))
          {
            MergedManifest = manifestItem;

            break;
          }
        }

        if (MergedManifest == null)
        {
          Log.LogError ("Could not find 'primary' manifest in provided list of project manifests. Expected: " + primaryManifestFullPath);

          return false;
        }

        // 
        // Sanity check all manifests to ensure that there's not another which defines <application> that's not 'primary'.
        // 

        ITaskItem applicationManifest = null;

        foreach (ITaskItem manifestItem in ProjectManifests)
        {
          AndroidManifestDocument androidManifestDocument = new AndroidManifestDocument ();

          androidManifestDocument.Load (manifestItem.GetMetadata ("FullPath"));

          if (androidManifestDocument.IsApplication)
          {
            if (applicationManifest == null)
            {
              applicationManifest = manifestItem;

              break;
            }
            else
            {
              Log.LogError ("Found multiple AndroidManifest files which define an <application> node.");

              return false;
            }
          }
        }

        if ((applicationManifest != null) && (applicationManifest != MergedManifest))
        {
          Log.LogError ("Specified project manifest does not define an <application> node.");

          return false;
        }

        // 
        // Process other 'third-party' manifests merging required metadata.
        // 

        if (MergedManifest != null)
        {
          HashSet<string> extraPackages = new HashSet<string> ();

          HashSet<string> extraResourcePaths = new HashSet<string> ();

          extraResourcePaths.Add (MergedManifest.GetMetadata ("IncludeResourceDirectories"));

          foreach (ITaskItem item in ProjectManifests)
          {
            AndroidManifestDocument androidManifestDocument = new AndroidManifestDocument ();

            androidManifestDocument.Load (item.GetMetadata ("FullPath"));

            if (item == MergedManifest)
            {
              PackageName = androidManifestDocument.PackageName;
            }
            else
            {
              if (!extraPackages.Contains (androidManifestDocument.PackageName))
              {
                extraPackages.Add (androidManifestDocument.PackageName);
              }

              if (!extraResourcePaths.Contains (item.GetMetadata ("IncludeResourceDirectories")))
              {
                extraResourcePaths.Add (item.GetMetadata ("IncludeResourceDirectories"));
              }
            }
          }

          MergedManifest.SetMetadata ("ExtraPackages", String.Join (":", extraPackages.ToArray ()));

          MergedManifest.SetMetadata ("IncludeResourceDirectories", String.Join (";", extraResourcePaths.ToArray ()));

          return true;
        }
      }
      catch (Exception e)
      {
        Log.LogErrorFromException (e, true);
      }

      return false;
    }